diff --git a/CasRandomizer.sln b/CasRandomizer.sln
index 4f1258537..510fe4df5 100644
--- a/CasRandomizer.sln
+++ b/CasRandomizer.sln
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Randomizer.PatchBuilder", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Randomizer.CrossPlatform", "src\Randomizer.CrossPlatform\Randomizer.CrossPlatform.csproj", "{B18DD122-3612-4948-B512-464D410A271B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Randomizer.Abstractions", "src\Randomizer.Abstractions\Randomizer.Abstractions.csproj", "{C5CED4F3-F57F-4651-88FB-5774326654E7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -107,6 +109,10 @@ Global
{B18DD122-3612-4948-B512-464D410A271B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B18DD122-3612-4948-B512-464D410A271B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B18DD122-3612-4948-B512-464D410A271B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5CED4F3-F57F-4651-88FB-5774326654E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5CED4F3-F57F-4651-88FB-5774326654E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5CED4F3-F57F-4651-88FB-5774326654E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5CED4F3-F57F-4651-88FB-5774326654E7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Randomizer.Abstractions/IAutoTracker.cs b/src/Randomizer.Abstractions/IAutoTracker.cs
new file mode 100644
index 000000000..761cb2b94
--- /dev/null
+++ b/src/Randomizer.Abstractions/IAutoTracker.cs
@@ -0,0 +1,100 @@
+using Randomizer.Data.Options;
+using Randomizer.Data.Tracking;
+using Randomizer.Shared.Enums;
+
+namespace Randomizer.Abstractions;
+
+public interface IAutoTracker
+{
+ ///
+ /// The tracker associated with this auto tracker
+ ///
+ public ITracker Tracker { get; }
+
+ ///
+ /// The type of connector that the auto tracker is currently using
+ ///
+ public EmulatorConnectorType ConnectorType { get; }
+
+ ///
+ /// The game that the player is currently in
+ ///
+ public Game CurrentGame { get; }
+
+ ///
+ /// The latest state that the player in LTTP (location, health, etc.)
+ ///
+ public AutoTrackerZeldaState? ZeldaState { get; }
+
+ ///
+ /// The latest state that the player in Super Metroid (location, health, etc.)
+ ///
+ public AutoTrackerMetroidState? MetroidState { get; }
+
+ ///
+ /// Disables the current connector and creates the requested type
+ ///
+ public void SetConnector(EmulatorConnectorType type, string? qusb2SnesIp);
+
+ ///
+ /// Occurs when the tracker's auto tracker is enabled
+ ///
+ public event EventHandler? AutoTrackerEnabled;
+
+ ///
+ /// Occurs when the tracker's auto tracker is disabled
+ ///
+ public event EventHandler? AutoTrackerDisabled;
+
+ ///
+ /// Occurs when the tracker's auto tracker is connected
+ ///
+ public event EventHandler? AutoTrackerConnected;
+
+ ///
+ /// Occurs when the tracker's auto tracker is disconnected
+ ///
+ public event EventHandler? AutoTrackerDisconnected;
+
+ ///
+ /// The action to run when the player asks Tracker to look at the game
+ ///
+ public AutoTrackerViewedAction? LatestViewAction { get; set; }
+
+ ///
+ /// If a connector is currently enabled
+ ///
+ public bool IsEnabled { get; }
+
+ ///
+ /// If a connector is currently connected to the emulator
+ ///
+ public bool IsConnected { get; }
+
+ ///
+ /// If a connector is currently connected to the emulator and a valid game state is detected
+ ///
+ public bool HasValidState { get; }
+
+ ///
+ /// If the auto tracker is currently sending messages
+ ///
+ public bool IsSendingMessages { get; }
+
+ ///
+ /// If the player currently has a fairy
+ ///
+ public bool PlayerHasFairy { get; }
+
+ ///
+ /// If the user is activately in an SMZ3 rom
+ ///
+ public bool IsInSMZ3 { get; }
+
+ ///
+ /// Writes a particular action to the emulator memory
+ ///
+ /// The action to write to memory
+ public void WriteToMemory(EmulatorAction action);
+
+}
diff --git a/src/Randomizer.Abstractions/IGameService.cs b/src/Randomizer.Abstractions/IGameService.cs
new file mode 100644
index 000000000..16a3f8264
--- /dev/null
+++ b/src/Randomizer.Abstractions/IGameService.cs
@@ -0,0 +1,110 @@
+using Randomizer.Data.Tracking;
+using Randomizer.Data.WorldData;
+using Randomizer.Shared;
+
+namespace Randomizer.Abstractions;
+
+public interface IGameService
+{
+ ///
+ /// Updates memory values so both SM and Z3 will cancel any pending MSU resumes and play
+ /// all tracks from the start until new resume points have been stored.
+ ///
+ /// True, even if it didn't do anything
+ public void TryCancelMsuResume();
+
+ ///
+ /// Gives an item to the player
+ ///
+ /// The item to give
+ /// The id of the player giving the item to the player (null for tracker)
+ /// False if it is currently unable to give an item to the player
+ public bool TryGiveItem(Item item, int? fromPlayerId);
+
+ ///
+ /// Gives a series of items to the player
+ ///
+ /// The list of items to give to the player
+ /// The id of the player giving the item to the player
+ /// False if it is currently unable to give an item to the player
+ public bool TryGiveItems(List- items, int fromPlayerId);
+
+ ///
+ /// Gives a series of item types from particular players
+ ///
+ /// The list of item types and the players that are giving the item to the player
+ /// False if it is currently unable to give the items to the player
+ public bool TryGiveItemTypes(List<(ItemType type, int fromPlayerId)> items);
+
+ ///
+ /// Restores the player to max health
+ ///
+ /// False if it is currently unable to give an item to the player
+ public bool TryHealPlayer();
+
+ ///
+ /// Fully fills the player's magic
+ ///
+ /// False if it is currently unable to give magic to the player
+ public bool TryFillMagic();
+
+ ///
+ /// Fully fills the player's bombs to capacity
+ ///
+ /// False if it is currently unable to give bombs to the player
+ public bool TryFillZeldaBombs();
+
+ ///
+ /// Fully fills the player's arrows
+ ///
+ /// False if it is currently unable to give arrows to the player
+ public bool TryFillArrows();
+
+ ///
+ /// Fully fills the player's rupees (sets to 2000)
+ ///
+ /// False if it is currently unable to give rupees to the player
+ public bool TryFillRupees();
+
+ ///
+ /// Fully fills the player's missiles
+ ///
+ /// False if it is currently unable to give missiles to the player
+ public bool TryFillMissiles();
+
+ ///
+ /// Fully fills the player's super missiles
+ ///
+ /// False if it is currently unable to give super missiles to the player
+ public bool TryFillSuperMissiles();
+
+ ///
+ /// Fully fills the player's power bombs
+ ///
+ /// False if it is currently unable to give power bombs to the player
+ public bool TryFillPowerBombs();
+
+ ///
+ /// Kills the player by removing their health and dealing damage to them
+ ///
+ /// True if successful
+ public bool TryKillPlayer();
+
+ ///
+ /// Sets the player to have the requirements for a crystal flash
+ ///
+ /// True if successful
+ public bool TrySetupCrystalFlash();
+
+ ///
+ /// Gives the player any items that tracker thinks they should have but are not in memory as having been gifted
+ ///
+ ///
+ public void SyncItems(EmulatorAction action);
+
+ ///
+ /// If the player was recently killed by the game service
+ ///
+ public bool PlayerRecentlyKilled { get; }
+
+}
diff --git a/src/Randomizer.Abstractions/IHistoryService.cs b/src/Randomizer.Abstractions/IHistoryService.cs
new file mode 100644
index 000000000..c034ae848
--- /dev/null
+++ b/src/Randomizer.Abstractions/IHistoryService.cs
@@ -0,0 +1,44 @@
+using Randomizer.Data.WorldData;
+using Randomizer.Shared.Enums;
+using Randomizer.Shared.Models;
+
+namespace Randomizer.Abstractions;
+
+///
+/// Service for managing the history of events through a playthrough
+///
+public interface IHistoryService
+{
+ ///
+ /// Adds an event to the history log
+ ///
+ /// The type of event
+ /// If this is an important event or not
+ /// The name of the event being logged
+ /// The optional location of where this event happened
+ /// The created event
+ public TrackerHistoryEvent AddEvent(HistoryEventType type, bool isImportant, string objectName, Location? location = null);
+
+ ///
+ /// Adds an event to the history log
+ ///
+ /// The event to add
+ public void AddEvent(TrackerHistoryEvent histEvent);
+
+ ///
+ /// Removes the event that was added last to the log
+ ///
+ public void RemoveLastEvent();
+
+ ///
+ /// Removes a specific event from the log
+ ///
+ /// The event to log
+ public void Remove(TrackerHistoryEvent histEvent);
+
+ ///
+ /// Retrieves the current history log
+ ///
+ /// The collection of events
+ public IReadOnlyCollection GetHistory();
+}
diff --git a/src/Randomizer.Abstractions/IItemService.cs b/src/Randomizer.Abstractions/IItemService.cs
new file mode 100644
index 000000000..0681fa52d
--- /dev/null
+++ b/src/Randomizer.Abstractions/IItemService.cs
@@ -0,0 +1,167 @@
+using Randomizer.Data.Configuration.ConfigTypes;
+using Randomizer.Data.WorldData;
+using Randomizer.Data.WorldData.Regions;
+using Randomizer.Shared;
+
+namespace Randomizer.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/Randomizer.Abstractions/ITracker.cs b/src/Randomizer.Abstractions/ITracker.cs
new file mode 100644
index 000000000..7cc28b84a
--- /dev/null
+++ b/src/Randomizer.Abstractions/ITracker.cs
@@ -0,0 +1,761 @@
+using MSURandomizerLibrary.Configs;
+using Randomizer.Data;
+using Randomizer.Data.Configuration.ConfigFiles;
+using Randomizer.Data.Configuration.ConfigTypes;
+using Randomizer.Data.Services;
+using Randomizer.Data.Tracking;
+using Randomizer.Data.WorldData;
+using Randomizer.Data.WorldData.Regions;
+using Randomizer.Shared;
+using Randomizer.Shared.Models;
+
+namespace Randomizer.Abstractions;
+
+public interface ITracker
+{
+ ///
+ /// Occurs when any speech was recognized, regardless of configured
+ /// thresholds.
+ ///
+ public event EventHandler? SpeechRecognized;
+
+ ///
+ /// Occurs when one more 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 the last action was undone.
+ ///
+ public event EventHandler? ActionUndone;
+
+ ///
+ /// Occurs when the tracker state has been loaded.
+ ///
+ public event EventHandler? StateLoaded;
+
+ ///
+ /// 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 the current played track number is updated
+ ///
+ public event EventHandler? TrackNumberUpdated;
+
+ ///
+ /// Occurs when the current track has changed
+ ///
+ public event EventHandler? TrackChanged;
+
+ ///
+ /// Set when the progression needs to be updated for the current tracker
+ /// instance
+ ///
+ public bool UpdateTrackerProgression { get; set; }
+
+ ///
+ /// Gets extra information about locations.
+ ///
+ public IMetadataService Metadata { get; }
+
+ ///
+ /// Gets a reference to the .
+ ///
+ public IItemService ItemService { get; }
+
+ ///
+ /// The number of pegs that have been pegged for Peg World mode
+ ///
+ public int PegsPegged { get; set; }
+
+ ///
+ /// Gets the world for the currently tracked playthrough.
+ ///
+ public World World { get; }
+
+ ///
+ /// Indicates whether Tracker is in Go Mode.
+ ///
+ public bool GoMode { get; }
+
+ ///
+ /// Indicates whether Tracker is in Peg World mode.
+ ///
+ public bool PegWorldMode { get; set; }
+
+ ///
+ /// Indicates whether Tracker is in Shaktool mode.
+ ///
+ public bool ShaktoolMode { get; set; }
+
+ ///
+ /// If the speech recognition engine was fully initialized
+ ///
+ public bool MicrophoneInitialized { get; }
+
+ ///
+ /// If voice recognition has been enabled or not
+ ///
+ public bool VoiceRecognitionEnabled { get; }
+
+ ///
+ /// Gets the configured responses.
+ ///
+ public ResponseConfig Responses { get; }
+
+ ///
+ /// Gets a collection of basic requests and responses.
+ ///
+ public IReadOnlyCollection Requests { get; }
+
+ ///
+ /// Gets a dictionary containing the rules and the various speech
+ /// recognition syntaxes.
+ ///
+ public IReadOnlyDictionary> Syntax { get; }
+
+ ///
+ /// Gets the tracking preferences.
+ ///
+ public TrackerOptions Options { get;}
+
+ ///
+ /// The generated rom
+ ///
+ public GeneratedRom? Rom { get; }
+
+ ///
+ /// The path to the generated rom
+ ///
+ public string? RomPath { get; }
+
+ ///
+ /// The region the player is currently in according to the Auto Tracker
+ ///
+ public RegionInfo? CurrentRegion { get; }
+
+ ///
+ /// The map to display for the player
+ ///
+ public string CurrentMap { get; }
+
+ ///
+ /// The current track number being played
+ ///
+ public int CurrentTrackNumber { get; }
+
+ ///
+ /// Gets a string describing tracker's mood.
+ ///
+ public string Mood { get;}
+ ///
+ /// Get if the Tracker has been updated since it was last saved
+ ///
+ public bool IsDirty { get; set; }
+
+ ///
+ /// The Auto Tracker for the Tracker
+ ///
+ public IAutoTracker? AutoTracker { get; set; }
+
+ ///
+ /// Service that handles modifying the game via auto tracker
+ ///
+ public IGameService? GameService { get; set; }
+
+ ///
+ /// Module that houses the history
+ ///
+ public IHistoryService History { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether Tracker may give hints when
+ /// asked about items or locations.
+ ///
+ public bool HintsEnabled { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether Tracker may give spoilers
+ /// when asked about items or locations.
+ ///
+ public bool SpoilersEnabled { get; set; }
+
+ ///
+ /// Gets if the local player has beaten the game or not
+ ///
+ public bool HasBeatenGame { get; }
+
+ ///
+ /// Attempts to replace a user name with a pronunciation-corrected
+ /// version of it.
+ ///
+ /// The user name to correct.
+ ///
+ /// The corrected user name, or .
+ ///
+ public string CorrectUserNamePronunciation(string userName);
+
+ ///
+ /// Initializes the microphone from the default audio device
+ ///
+ ///
+ /// True if the microphone is initialized, false otherwise
+ ///
+ public bool InitializeMicrophone();
+
+ ///
+ /// Loads the state from the database for a given rom
+ ///
+ /// The rom to load
+ /// The full path to the rom to load
+ /// True or false if the load was successful
+ public bool Load(GeneratedRom rom, string romPath);
+
+ ///
+ /// Saves the state of the tracker to the database
+ ///
+ ///
+ public Task SaveAsync();
+
+ ///
+ /// Undoes the last operation.
+ ///
+ /// The speech recognition confidence.
+ public void Undo(float confidence);
+
+ ///
+ /// Toggles Go Mode on.
+ ///
+ /// The speech recognition confidence.
+ public 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 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 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 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.
+ public void SetDungeonRequirement(IDungeon dungeon, ItemType? medallion = null, float? confidence = null);
+
+ ///
+ /// Starts voice recognition.
+ ///
+ public bool TryStartTracking();
+
+ ///
+ /// Connects Tracker to chat.
+ ///
+ /// The user name to connect as.
+ ///
+ /// The OAuth token for .
+ ///
+ ///
+ /// The channel to monitor for incoming messages.
+ ///
+ ///
+ /// The is for .
+ ///
+ public void ConnectToChat(string? userName, string? oauthToken, string? channel, string? id);
+
+ ///
+ /// Sets the start time of the timer
+ ///
+ public void StartTimer(bool isInitial = false);
+
+ ///
+ /// Resets the timer to 0
+ ///
+ public void ResetTimer(bool isInitial = false);
+
+ ///
+ /// Pauses the timer, saving the elapsed time
+ ///
+ public Action? PauseTimer(bool addUndo = true);
+
+ ///
+ /// Pauses or resumes the timer based on if it is
+ /// currently paused or not
+ ///
+ public void ToggleTimer();
+
+ ///
+ /// Stops voice recognition.
+ ///
+ public void StopTracking();
+
+ ///
+ /// Enables the voice recognizer if the microphone is enabled
+ ///
+ public void EnableVoiceRecognition();
+
+ ///
+ /// Disables voice recognition if it was previously enabled
+ ///
+ public void DisableVoiceRecognition();
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// The possible sentences to speak.
+ ///
+ /// true if a sentence was spoken, false if was null.
+ ///
+ public bool Say(SchrodingersString? text);
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// Selects the response to use.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public bool Say(Func selectResponse);
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// The possible sentences to speak.
+ /// The arguments used to format the text.
+ ///
+ /// true if a sentence was spoken, false if was null.
+ ///
+ public bool Say(SchrodingersString? text, params object?[] args);
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// Selects the response to use.
+ /// The arguments used to format the text.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public bool Say(Func selectResponse, params object?[] args);
+
+ ///
+ /// Speak a sentence using text-to-speech only one time.
+ ///
+ /// The possible sentences to speak.
+ ///
+ /// true if a sentence was spoken, false if was null.
+ ///
+ public bool SayOnce(SchrodingersString? text);
+
+ ///
+ /// Speak a sentence using text-to-speech only one time.
+ ///
+ /// Selects the response to use.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public bool SayOnce(Func selectResponse)
+ {
+ return SayOnce(selectResponse(Responses));
+ }
+ ///
+ /// Speak a sentence using text-to-speech only one time.
+ ///
+ /// Selects the response to use.
+ /// The arguments used to format the text.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public bool SayOnce(Func selectResponse, params object?[] args);
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// The phrase to speak.
+ ///
+ /// true to wait until the text has been spoken completely or
+ /// false to immediately return. The default is false.
+ ///
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public bool Say(string? text, bool wait = false);
+
+ ///
+ /// Repeats the most recently spoken sentence using text-to-speech at a
+ /// slower rate.
+ ///
+ public void Repeat();
+
+ ///
+ /// Makes Tracker stop talking.
+ ///
+ public void ShutUp();
+
+ ///
+ /// Notifies the user an error occurred.
+ ///
+ public 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 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 void TrackItems(List
- items, bool autoTracked, bool giftedItem);
+
+ ///
+ /// Removes an item from the tracker.
+ ///
+ /// The item to untrack.
+ /// The speech recognition confidence.
+ public 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 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 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 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 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 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 void Clear(Location location, float? confidence = null, bool autoTracked = 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 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 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 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.
+ public void MarkLocation(Location location, Item item, float? confidence = null);
+
+ ///
+ /// Pegs a Peg World peg.
+ ///
+ /// The speech recognition confidence.
+ public void Peg(float? confidence = null);
+
+ ///
+ /// 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);
+
+ ///
+ /// 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 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(RegionInfo? 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 RestartIdleTimers();
+
+ ///
+ /// Adds an action to be invoked to undo the last operation.
+ ///
+ ///
+ /// The action to invoke to undo the last operation.
+ ///
+ public 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 bool IsWorth(RewardType reward);
+
+ ///
+ /// Formats a string so that it will be pronounced correctly by the
+ /// text-to-speech engine.
+ ///
+ /// The text to correct.
+ /// A string with the pronunciations replaced.
+ 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 bool IsWorth(Item item);
+}
diff --git a/src/Randomizer.Abstractions/Randomizer.Abstractions.csproj b/src/Randomizer.Abstractions/Randomizer.Abstractions.csproj
new file mode 100644
index 000000000..fd3eefa3e
--- /dev/null
+++ b/src/Randomizer.Abstractions/Randomizer.Abstractions.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/Randomizer.App/App.xaml.cs b/src/Randomizer.App/App.xaml.cs
index ea97f442a..0acb24f78 100644
--- a/src/Randomizer.App/App.xaml.cs
+++ b/src/Randomizer.App/App.xaml.cs
@@ -14,6 +14,7 @@
using MSURandomizerLibrary.Models;
using MSURandomizerLibrary.Services;
using MSURandomizerUI;
+using Randomizer.Abstractions;
using Randomizer.App.Controls;
using Randomizer.App.Windows;
using Randomizer.Data.Configuration;
@@ -112,12 +113,12 @@ protected static void ConfigureServices(IServiceCollection services)
.AddOptionalModule()
.AddOptionalModule()
.AddOptionalModule()
- .AddOptionalModule()
- .AddOptionalModule();
+ .AddOptionalModule();
+ services.AddScoped();
services.AddSingleton();
services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
services.AddSingleton();
services.AddMultiplayerServices();
services.AddSingleton();
diff --git a/src/Randomizer.App/Controls/RomListPanel.cs b/src/Randomizer.App/Controls/RomListPanel.cs
index 01d767b15..6eb96509b 100644
--- a/src/Randomizer.App/Controls/RomListPanel.cs
+++ b/src/Randomizer.App/Controls/RomListPanel.cs
@@ -14,6 +14,7 @@
using Randomizer.SMZ3.Generation;
using Randomizer.SMZ3.Infrastructure;
using Randomizer.SMZ3.Tracking;
+using Randomizer.SMZ3.Tracking.Services;
namespace Randomizer.App.Controls
{
diff --git a/src/Randomizer.App/TrackerLocationSyncer.cs b/src/Randomizer.App/TrackerLocationSyncer.cs
index 7cf761bb0..f735c5ad6 100644
--- a/src/Randomizer.App/TrackerLocationSyncer.cs
+++ b/src/Randomizer.App/TrackerLocationSyncer.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
using Randomizer.Data.WorldData;
using Randomizer.SMZ3.Tracking;
using Randomizer.SMZ3.Tracking.Services;
@@ -24,7 +25,7 @@ public class TrackerLocationSyncer
/// Service for retrieving the item data
/// Service for retrieving world data
/// Logger
- public TrackerLocationSyncer(Tracker tracker, IItemService itemService, IWorldService worldService, ILogger logger)
+ public TrackerLocationSyncer(ITracker tracker, IItemService itemService, IWorldService worldService, ILogger logger)
{
Tracker = tracker;
ItemService = itemService;
@@ -82,7 +83,7 @@ public bool ShowOutOfLogicLocations
}
}
- public Tracker Tracker { get; private set; }
+ public ITracker Tracker { get; private set; }
public IItemService ItemService { get; private set; }
diff --git a/src/Randomizer.App/Windows/GenerateRomWindow.xaml b/src/Randomizer.App/Windows/GenerateRomWindow.xaml
index 1fcaa3fd1..f5454fab4 100644
--- a/src/Randomizer.App/Windows/GenerateRomWindow.xaml
+++ b/src/Randomizer.App/Windows/GenerateRomWindow.xaml
@@ -9,6 +9,7 @@
xmlns:shared="clr-namespace:Randomizer.Shared;assembly=Randomizer.Shared"
xmlns:options="clr-namespace:Randomizer.Data.Options;assembly=Randomizer.Data"
xmlns:windows="clr-namespace:Randomizer.App.Windows"
+ xmlns:enums="clr-namespace:Randomizer.Shared.Enums;assembly=Randomizer.Shared"
mc:Ignorable="d"
Loaded="GenerateRomWindow_OnLoaded"
Closing="Window_Closing"
@@ -66,7 +67,7 @@
diff --git a/src/Randomizer.App/Windows/MsuTrackWindow.xaml.cs b/src/Randomizer.App/Windows/MsuTrackWindow.xaml.cs
index 367f15fd5..6775fc4d9 100644
--- a/src/Randomizer.App/Windows/MsuTrackWindow.xaml.cs
+++ b/src/Randomizer.App/Windows/MsuTrackWindow.xaml.cs
@@ -6,7 +6,9 @@
using System.Windows.Controls;
using System.Windows.Media.Animation;
using MSURandomizerLibrary.Configs;
+using Randomizer.Abstractions;
using Randomizer.Data.Options;
+using Randomizer.Data.Tracking;
using Randomizer.SMZ3.Tracking;
namespace Randomizer.App.Windows;
@@ -15,7 +17,7 @@ public partial class MsuTrackWindow : Window, IDisposable
{
private readonly DoubleAnimation _marquee = new();
private CancellationTokenSource _cts = new();
- private Tracker? _tracker;
+ private ITracker? _tracker;
private RandomizerOptions? _options;
private Track? _currentTrack;
private Msu? _currentMsu;
@@ -29,7 +31,7 @@ public MsuTrackWindow()
App.RestoreWindowPositionAndSize(this);
}
- public void Init(Tracker tracker, RandomizerOptions options)
+ public void Init(ITracker tracker, RandomizerOptions options)
{
_tracker = tracker;
_options = options;
diff --git a/src/Randomizer.App/Windows/TrackerHelpWindow.xaml.cs b/src/Randomizer.App/Windows/TrackerHelpWindow.xaml.cs
index 36c39cac8..a70ea7dfb 100644
--- a/src/Randomizer.App/Windows/TrackerHelpWindow.xaml.cs
+++ b/src/Randomizer.App/Windows/TrackerHelpWindow.xaml.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Windows;
-
+using Randomizer.Abstractions;
using Randomizer.SMZ3.Tracking;
namespace Randomizer.App
@@ -11,7 +11,7 @@ namespace Randomizer.App
///
public partial class TrackerHelpWindow : Window
{
- public TrackerHelpWindow(Tracker tracker)
+ public TrackerHelpWindow(ITracker tracker)
{
SpeechRecognitionSyntax = tracker.Syntax;
diff --git a/src/Randomizer.App/Windows/TrackerMapWindow.xaml.cs b/src/Randomizer.App/Windows/TrackerMapWindow.xaml.cs
index bd0766e2d..08762345b 100644
--- a/src/Randomizer.App/Windows/TrackerMapWindow.xaml.cs
+++ b/src/Randomizer.App/Windows/TrackerMapWindow.xaml.cs
@@ -7,6 +7,7 @@
using System.Windows.Input;
using System.Windows.Shapes;
using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
using Randomizer.App.ViewModels;
using Randomizer.Data.Configuration.ConfigFiles;
using Randomizer.Data.Configuration.ConfigTypes;
@@ -23,7 +24,7 @@ namespace Randomizer.App.Windows
public partial class TrackerMapWindow : Window
{
private readonly ILogger _logger;
- private readonly Tracker _tracker;
+ private readonly ITracker _tracker;
private TrackerLocationSyncer _syncer;
///
@@ -37,7 +38,7 @@ public partial class TrackerMapWindow : Window
/// The config for the map json file with all the location details
///
/// Logger for logging
- public TrackerMapWindow(Tracker tracker, TrackerLocationSyncer syncer, TrackerMapConfig config, ILogger logger)
+ public TrackerMapWindow(ITracker tracker, TrackerLocationSyncer syncer, TrackerMapConfig config, ILogger logger)
{
InitializeComponent();
diff --git a/src/Randomizer.App/Windows/TrackerWindow.xaml.cs b/src/Randomizer.App/Windows/TrackerWindow.xaml.cs
index 066f52a2d..89261fe33 100644
--- a/src/Randomizer.App/Windows/TrackerWindow.xaml.cs
+++ b/src/Randomizer.App/Windows/TrackerWindow.xaml.cs
@@ -16,8 +16,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MSURandomizerLibrary.Services;
+using Randomizer.Abstractions;
using Randomizer.Data.Configuration.ConfigTypes;
using Randomizer.Data.Options;
+using Randomizer.Data.Tracking;
using Randomizer.Data.WorldData;
using Randomizer.Data.WorldData.Regions;
using Randomizer.Data.WorldData.Regions.Zelda;
@@ -64,7 +66,7 @@ public partial class TrackerWindow : Window
private UILayout _layout;
private readonly UILayout _defaultLayout;
private UILayout? _previousLayout;
- private Tracker? _tracker;
+ private ITracker? _tracker;
public TrackerWindow(IServiceProvider serviceProvider,
IItemService itemService,
@@ -118,7 +120,7 @@ protected enum Origin
BottomRight = 3
}
- public Tracker Tracker => _tracker ?? throw new InvalidOperationException("Tracker not created");
+ public ITracker Tracker => _tracker ?? throw new InvalidOperationException("Tracker not created");
public GeneratedRom? Rom { get; set; }
@@ -725,7 +727,7 @@ private void InitializeTracker()
_msuLookupService.LookupMsus(_options.GeneralOptions.MsuPath);
});
- _tracker = _serviceProvider.GetRequiredService();
+ _tracker = _serviceProvider.GetRequiredService();
// If a rom was passed in with a valid tracker state, reload the state from the database
if (GeneratedRom.IsValid(Rom))
@@ -987,7 +989,11 @@ private void Window_Closed(object sender, EventArgs e)
_msuTrackWindow?.Close(true);
_msuTrackWindow = null;
_msuTrackWindow?.Dispose();
- _tracker?.Dispose();
+
+ if (_tracker is IDisposable disposable)
+ {
+ disposable.Dispose();
+ }
}
private void LocationsMenuItem_Click(object sender, RoutedEventArgs e)
diff --git a/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs b/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs
index ee0541345..5f6d9b3c1 100644
--- a/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs
+++ b/src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs
@@ -1,6 +1,8 @@
using System.Text;
using Microsoft.Extensions.DependencyInjection;
+using Randomizer.Abstractions;
using Randomizer.Data.Options;
+using Randomizer.Data.Tracking;
using Randomizer.Data.WorldData;
using Randomizer.Data.WorldData.Regions;
using Randomizer.Shared;
@@ -20,7 +22,7 @@ public class ConsoleTrackerDisplayService
private readonly TrackerOptionsAccessor _trackerOptionsAccessor;
private readonly RandomizerOptions _options;
private readonly IServiceProvider _serviceProvider;
- private Tracker _tracker = null!;
+ private ITracker _tracker = null!;
private World _world = null!;
private IWorldService _worldService = null!;
private Region _lastRegion = null!;
@@ -40,7 +42,7 @@ public async Task StartTracking(GeneratedRom rom, string romPath)
_trackerOptionsAccessor.Options = _options.GeneralOptions.GetTrackerOptions();
_world = _romLoaderService.LoadGeneratedRom(rom).First(x => x.IsLocalWorld);
_worldService = _serviceProvider.GetRequiredService();
- _tracker = _serviceProvider.GetRequiredService();
+ _tracker = _serviceProvider.GetRequiredService();
_tracker.Load(rom, romPath);
_tracker.TryStartTracking();
_tracker.AutoTracker?.SetConnector(_options.AutoTrackerDefaultConnector, _options.AutoTrackerQUsb2SnesIp);
diff --git a/src/Randomizer.CrossPlatform/ServiceCollectionExtensions.cs b/src/Randomizer.CrossPlatform/ServiceCollectionExtensions.cs
index e21742e6c..8f05c8422 100644
--- a/src/Randomizer.CrossPlatform/ServiceCollectionExtensions.cs
+++ b/src/Randomizer.CrossPlatform/ServiceCollectionExtensions.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using MSURandomizerLibrary;
+using Randomizer.Abstractions;
using Randomizer.Data.Configuration;
using Randomizer.Data.Options;
using Randomizer.Data.Services;
@@ -24,9 +25,9 @@ public static IServiceCollection ConfigureServices(this IServiceCollection servi
.AddOptionalModule()
.AddOptionalModule()
.AddOptionalModule()
- .AddOptionalModule()
- .AddOptionalModule();
- services.AddScoped();
+ .AddOptionalModule();
+ services.AddScoped();
+ services.AddScoped();
services.AddSingleton();
services.AddMultiplayerServices();
diff --git a/src/Randomizer.Data/Options/Config.cs b/src/Randomizer.Data/Options/Config.cs
index 50aadd0ac..39cc51fdc 100644
--- a/src/Randomizer.Data/Options/Config.cs
+++ b/src/Randomizer.Data/Options/Config.cs
@@ -11,6 +11,7 @@
using Randomizer.Data.Logic;
using Randomizer.Data.WorldData.Regions;
using Randomizer.Shared;
+using Randomizer.Shared.Enums;
using Randomizer.Shared.Multiplayer;
using JsonSerializer = System.Text.Json.JsonSerializer;
diff --git a/src/Randomizer.Data/Options/PlandoConfig.cs b/src/Randomizer.Data/Options/PlandoConfig.cs
index 35cc23508..b23a23978 100644
--- a/src/Randomizer.Data/Options/PlandoConfig.cs
+++ b/src/Randomizer.Data/Options/PlandoConfig.cs
@@ -7,6 +7,7 @@
using YamlDotNet.Serialization;
using Randomizer.Data.Logic;
+using Randomizer.Shared.Enums;
namespace Randomizer.Data.Options
{
diff --git a/src/Randomizer.Data/Options/RandomizerOptions.cs b/src/Randomizer.Data/Options/RandomizerOptions.cs
index 6b9adba5d..018b199e0 100644
--- a/src/Randomizer.Data/Options/RandomizerOptions.cs
+++ b/src/Randomizer.Data/Options/RandomizerOptions.cs
@@ -107,7 +107,9 @@ public static RandomizerOptions Load(string loadPath, string savePath, bool isYa
if (isYaml)
{
- var serializer = new YamlDotNet.Serialization.Deserializer();
+ var serializer = new DeserializerBuilder()
+ .IgnoreUnmatchedProperties()
+ .Build();
var options = serializer.Deserialize(fileText);
options.FilePath = savePath;
return options;
diff --git a/src/Randomizer.Data/Tracking/AutoTrackerMetroidState.cs b/src/Randomizer.Data/Tracking/AutoTrackerMetroidState.cs
new file mode 100644
index 000000000..75054aa67
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/AutoTrackerMetroidState.cs
@@ -0,0 +1,115 @@
+namespace Randomizer.Data.Tracking;
+
+///
+/// Used to retrieve certain states based on the memory in Metroid
+/// Seee https://jathys.zophar.net/supermetroid/kejardon/RAMMap.txt for details on the memory
+///
+public class AutoTrackerMetroidState
+{
+ private readonly EmulatorMemoryData _data;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public AutoTrackerMetroidState(EmulatorMemoryData data)
+ {
+ _data = data;
+ }
+
+ ///
+ /// The overall room number the player is in
+ ///
+ public int CurrentRoom => _data.ReadUInt8(0x7E079B - 0x7E0750);
+
+ ///
+ /// The region room number the player is in
+ ///
+ public int CurrentRoomInRegion => _data.ReadUInt8(0x7E079D - 0x7E0750);
+
+ ///
+ /// The current region the player is in
+ ///
+ public int CurrentRegion => _data.ReadUInt8(0x7E079F - 0x7E0750);
+
+ ///
+ /// The amount of energy/health
+ ///
+ public int Energy => _data.ReadUInt16(0x7E09C2 - 0x7E0750);
+
+ ///
+ /// The amount currently in reserve tanks
+ ///
+ public int ReserveTanks => _data.ReadUInt16(0x7E09D6 - 0x7E0750);
+
+ ///
+ /// The max of health
+ ///
+ public int MaxEnergy => _data.ReadUInt16(0x7E09C4 - 0x7E0750);
+
+ ///
+ /// The max in reserve tanks
+ ///
+ public int MaxReserveTanks => _data.ReadUInt16(0x7E09D4 - 0x7E0750);
+
+ ///
+ /// Samus's X Location
+ ///
+ public int SamusX => _data.ReadUInt16(0x7E0AF6 - 0x7E0750);
+
+ ///
+ /// Samus's Y Location
+ ///
+ public int SamusY => _data.ReadUInt16(0x7E0AFA - 0x7E0750);
+
+ ///
+ /// Samus's current super missile count
+ ///
+ public int SuperMissiles => _data.ReadUInt8(0x7E09CA - 0x7E0750);
+
+ ///
+ /// Samus's max super missile count
+ ///
+ public int MaxSuperMissiles => _data.ReadUInt8(0x7E09CC - 0x7E0750);
+
+ ///
+ /// Samus's current missile count
+ ///
+ public int Missiles => _data.ReadUInt8(0x7E09C6 - 0x7E0750);
+
+ ///
+ /// Samus's max missile count
+ ///
+ public int MaxMissiles => _data.ReadUInt8(0x7E09C8 - 0x7E0750);
+
+ ///
+ /// Samus's current power bomb count
+ ///
+ public int PowerBombs => _data.ReadUInt8(0x7E09CE - 0x7E0750);
+
+ ///
+ /// Samus's max power bomb count
+ ///
+ public int MaxPowerBombs => _data.ReadUInt8(0x7E09D0 - 0x7E0750);
+
+ public bool IsSamusInArea(int minX, int maxX, int minY, int maxY)
+ {
+ return SamusX >= minX && SamusX <= maxX && SamusY >= minY && SamusY <= 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;
+
+ ///
+ /// Prints debug data for the state
+ ///
+ ///
+ public override string ToString()
+ {
+ return $"CurrentRoom: {CurrentRoom} | CurrentRoomInRegion: {CurrentRoomInRegion} | CurrentRegion: {CurrentRegion} | Health: {Energy},{ReserveTanks} | X,Y {SamusX},{SamusY}";
+ }
+}
diff --git a/src/Randomizer.Data/Tracking/AutoTrackerViewedAction.cs b/src/Randomizer.Data/Tracking/AutoTrackerViewedAction.cs
new file mode 100644
index 000000000..a6c075a90
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/AutoTrackerViewedAction.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Class for storing an action from viewing something
+/// for a short amount of time
+///
+public class AutoTrackerViewedAction
+{
+ private Action? _action;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public AutoTrackerViewedAction(Action action)
+ {
+ _action = action;
+ _ = ExpireAsync();
+ }
+
+ ///
+ /// Expires the action after a period of time
+ ///
+ private async Task ExpireAsync()
+ {
+ await Task.Delay(TimeSpan.FromSeconds(15));
+ _action = null;
+ }
+
+ ///
+ /// Invokes the action, if it's still valid
+ ///
+ ///
+ public bool Invoke()
+ {
+ if (_action == null) return false;
+ _action.Invoke();
+ return true;
+ }
+
+ ///
+ /// If the action is valid
+ ///
+ public bool IsValid => _action != null;
+}
diff --git a/src/Randomizer.Data/Tracking/AutoTrackerZeldaState.cs b/src/Randomizer.Data/Tracking/AutoTrackerZeldaState.cs
new file mode 100644
index 000000000..3ce3aeebb
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/AutoTrackerZeldaState.cs
@@ -0,0 +1,102 @@
+namespace Randomizer.Data.Tracking;
+
+///
+/// Used to retrieve certain states based on the memory in Zelda
+/// See http://alttp.run/hacking/index.php?title=RAM:_Bank_0x7E:_Page_0x00 for details on the memory
+/// These memory address values are the offset from 0x7E0000
+///
+public class AutoTrackerZeldaState
+{
+ private readonly EmulatorMemoryData _data;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public AutoTrackerZeldaState(EmulatorMemoryData data)
+ {
+ _data = data;
+ }
+
+ ///
+ /// The current room the player is in
+ ///
+ public int CurrentRoom => ReadUInt16(0xA0);
+
+ ///
+ /// The previous room the player was in
+ ///
+ public int PreviousRoom => ReadUInt16(0xA2);
+
+ ///
+ /// The state of the game (Overworld, Dungeon, etc.)
+ ///
+ public int State => ReadUInt8(0x10);
+
+ ///
+ /// The secondary state value
+ ///
+ public int Substate => ReadUInt8(0x11);
+
+ ///
+ /// The player's Y location
+ ///
+ public int LinkY => ReadUInt16(0x20);
+
+ ///
+ /// The player's X Location
+ ///
+ public int LinkX => ReadUInt16(0x22);
+
+ ///
+ /// What the player is currently doing
+ ///
+ public int LinkState => ReadUInt8(0x5D);
+
+ ///
+ /// Value used to determine if the player is in the light or dark world
+ /// Apparently this is used for other calculations as well, so need to be a bit careful
+ /// Transitioning from Super Metroid also seems to break this until you go through a portal
+ ///
+ public int OverworldValue => ReadUInt8(0x7B);
+
+ ///
+ /// True if Link is on the bottom half of the current room
+ ///
+ public bool IsOnBottomHalfOfRoom => ReadUInt8(0xAA) == 2;
+
+ ///
+ /// True if Link is on the right half of the current room
+ ///
+ public bool IsOnRightHalfOfRoom => ReadUInt8(0xA9) == 1;
+
+ ///
+ /// The overworld screen that the player is on
+ ///
+ public int OverworldScreen => ReadUInt16(0x8A);
+
+ ///
+ /// Reads a specific block of memory
+ ///
+ /// The address offset from 0x7E0000
+ ///
+ public int ReadUInt8(int address) => _data.ReadUInt8(address);
+
+ ///
+ /// Reads a specific block of memory
+ ///
+ /// The address offset from 0x7E0000
+ ///
+ public int ReadUInt16(int address) => _data.ReadUInt16(address);
+
+ ///
+ /// Get debug string
+ ///
+ ///
+ public override string ToString()
+ {
+ var vertical = IsOnBottomHalfOfRoom ? "Bottom" : "Top";
+ var horizontal = IsOnRightHalfOfRoom ? "Right" : "Left";
+ return $"Room: {PreviousRoom}->{CurrentRoom} ({vertical}{horizontal}) | State: {State}/{Substate} | X,Y: {LinkX},{LinkY} | LinkState: {LinkState} | OW Screen: {OverworldScreen}";
+ }
+}
diff --git a/src/Randomizer.Data/Tracking/BossTrackedEventArgs.cs b/src/Randomizer.Data/Tracking/BossTrackedEventArgs.cs
new file mode 100644
index 000000000..25f3b3cc8
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/BossTrackedEventArgs.cs
@@ -0,0 +1,27 @@
+using Randomizer.Data.WorldData;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Provides data for events that occur when clearing a location.
+///
+public class BossTrackedEventArgs : TrackerEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The boss that was tracked.
+ /// The speech recognition confidence.
+ /// If the location was automatically tracked
+ public BossTrackedEventArgs(Boss? boss, float? confidence, bool autoTracked)
+ : base(confidence, autoTracked)
+ {
+ Boss = boss;
+ }
+
+ ///
+ /// Gets the boss that was tracked.
+ ///
+ public Boss? Boss { get; }
+}
diff --git a/src/Randomizer.Data/Tracking/DungeonTrackedEventArgs.cs b/src/Randomizer.Data/Tracking/DungeonTrackedEventArgs.cs
new file mode 100644
index 000000000..2070ef346
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/DungeonTrackedEventArgs.cs
@@ -0,0 +1,27 @@
+using Randomizer.Data.WorldData.Regions;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Provides data for events that occur when tracking a dungeon.
+///
+public class DungeonTrackedEventArgs : TrackerEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The dungeon that was tracked.
+ /// The speech recognition confidence.
+ /// If the location was automatically tracked
+ public DungeonTrackedEventArgs(IDungeon? dungeon, float? confidence, bool autoTracked)
+ : base(confidence, autoTracked)
+ {
+ Dungeon = dungeon;
+ }
+
+ ///
+ /// Gets the boss that was tracked.
+ ///
+ public IDungeon? Dungeon { get; }
+}
diff --git a/src/Randomizer.Data/Tracking/EmulatorAction.cs b/src/Randomizer.Data/Tracking/EmulatorAction.cs
new file mode 100644
index 000000000..58116d574
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/EmulatorAction.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using Randomizer.Data.WorldData;
+using Randomizer.Shared.Enums;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Class used for communicating between the emulator and tracker
+///
+public class EmulatorAction
+{
+ ///
+ /// The action to be done by the emulator (read/write/etc)
+ ///
+ public EmulatorActionType Type { get; set; }
+
+ ///
+ /// The starting memory address
+ ///
+ public int Address { get; set; }
+
+ ///
+ /// The number of bytes to capture from the emulator
+ ///
+ public int Length { get; set; }
+
+ ///
+ /// Values for writing
+ ///
+ public ICollection? WriteValues { get; set; }
+
+ ///
+ /// The type of memory to read or modify (WRAM, CARTRAM, CARTROM)
+ ///
+ public MemoryDomain Domain { get; set; }
+
+ ///
+ /// The game this message is for
+ ///
+ public Game Game { get; set; } = Game.Both;
+
+ ///
+ /// Action to perform when getting a response for this from the emulator
+ ///
+ public Action? Action { get; set; }
+
+ ///
+ /// The previous data collected for this action
+ ///
+ public EmulatorMemoryData? PreviousData { get; protected set; }
+
+ ///
+ /// The latest data collected for this action
+ ///
+ public EmulatorMemoryData? CurrentData { get; protected set; }
+
+ ///
+ /// The amount of time that should happen between consecutive reads
+ ///
+ public double FrequencySeconds = 0;
+
+ ///
+ /// When this action was last executed
+ ///
+ public DateTime? LastRunTime;
+
+ ///
+ /// Update the stored data and invoke the action
+ ///
+ /// The data collected from the emulator
+ public void Invoke(EmulatorMemoryData data)
+ {
+ PreviousData = CurrentData;
+ CurrentData = data;
+ LastRunTime = DateTime.Now;
+ Action?.Invoke(this);
+ }
+
+ ///
+ /// If this message should be sent based on the game the player is currently in
+ ///
+ /// The game the player is currently in
+ /// If the player has actually started the game
+ /// True if the message should be sent.
+ public bool ShouldProcess(Game currentGame, bool hasStartedGame)
+ {
+ return HasExpired && ((!hasStartedGame && Game == Game.Neither) || (hasStartedGame && Game != Game.Neither && (Game == Game.Both || Game == currentGame)));
+ }
+
+ ///
+ /// If the previous action's result has expired
+ ///
+ public bool HasExpired
+ {
+ get
+ {
+ return FrequencySeconds <= 0 || LastRunTime == null ||
+ (DateTime.Now - LastRunTime.Value).TotalSeconds >= FrequencySeconds;
+ }
+ }
+
+ ///
+ /// Checks if the data has changed between the previous and current collections
+ ///
+ /// True if the data has changed, false otherwise
+ public bool HasDataChanged()
+ {
+ return CurrentData != null && !CurrentData.Equals(PreviousData);
+ }
+
+ ///
+ /// Cached set of locations for this action
+ ///
+ public ICollection? Locations { get; set; }
+
+ ///
+ /// Clears both the previous and current data sets
+ ///
+ public void ClearData()
+ {
+ PreviousData = null;
+ CurrentData = null;
+ }
+
+}
diff --git a/src/Randomizer.Data/Tracking/EmulatorMemoryData.cs b/src/Randomizer.Data/Tracking/EmulatorMemoryData.cs
new file mode 100644
index 000000000..c30aadcd7
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/EmulatorMemoryData.cs
@@ -0,0 +1,129 @@
+using System.Linq;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Class used to house byte data retrieved from the emulator at a given point in time
+/// Used to retrieve data at locations in memory
+///
+public class EmulatorMemoryData
+{
+ private byte[] _bytes;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public EmulatorMemoryData(byte[] bytes)
+ {
+ _bytes = bytes;
+ }
+
+ ///
+ /// The raw byte array of the data
+ ///
+ public byte[] Raw
+ {
+ get
+ {
+ return _bytes;
+ }
+ }
+
+ ///
+ /// Returns the memory value at a location
+ ///
+ /// The offset location to check
+ /// The value from the byte array at that location
+ public byte ReadUInt8(int location)
+ {
+ return _bytes[location];
+ }
+
+ ///
+ /// Gets the memory value for a location and returns if it matches a given flag
+ ///
+ /// The offset location to check
+ /// The flag to check against
+ /// True if the flag is set for the memory location.
+ public bool CheckBinary8Bit(int location, int flag)
+ {
+ return (ReadUInt8(location) & flag) == flag;
+ }
+
+ ///
+ /// Checks if a value in memory matches a flag or has been increased to denote obtaining an item
+ ///
+ /// The previous data to compare to
+ /// The offset location to check
+ /// The flag to check against
+ /// True if the value in memory was set or increased
+ public bool CompareUInt8(EmulatorMemoryData previousData, int location, int? flag)
+ {
+ var prevValue = previousData != null && previousData._bytes.Length > location ? previousData.ReadUInt8(location) : -1;
+ var newValue = ReadUInt8(location);
+
+ if (newValue > prevValue)
+ {
+ if (flag != null)
+ {
+ if ((newValue & flag) == flag)
+ {
+ return true;
+ }
+ }
+ else if (newValue > 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns the memory value of two bytes / sixteen bits. Note that these are flipped
+ /// from what you may expect if you're thinking of it in binary terms. The second byte
+ /// is actually multiplied by 0xFF / 256 and added to the first
+ ///
+ /// The offset location to check
+ /// The value from the byte array at that location
+ public int ReadUInt16(int location)
+ {
+ return _bytes[location + 1] * 256 + _bytes[location];
+ }
+
+ ///
+ /// Checks if a binary flag is set for a given set of two bytes / sixteen bits
+ ///
+ /// The offset location to check
+ /// The flag to check against
+ /// True if the flag is set for the memory location.
+ public bool CheckUInt16(int location, int flag)
+ {
+ var data = ReadUInt16(location);
+ var adjustedFlag = 1 << flag;
+ var temp = data & adjustedFlag;
+ return temp == adjustedFlag;
+ }
+
+ ///
+ /// Returns if this EmulatorMemoryData equals another
+ ///
+ ///
+ ///
+ public override bool Equals(object? other)
+ {
+ if (other is not EmulatorMemoryData otherData) return false;
+ return Enumerable.SequenceEqual(otherData._bytes, _bytes);
+ }
+
+ ///
+ /// Returns the hash code of the bytes array
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ return _bytes.GetHashCode();
+ }
+}
diff --git a/src/Randomizer.Data/Tracking/ItemTrackedEventArgs.cs b/src/Randomizer.Data/Tracking/ItemTrackedEventArgs.cs
new file mode 100644
index 000000000..6a24bf08c
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/ItemTrackedEventArgs.cs
@@ -0,0 +1,36 @@
+using Randomizer.Data.WorldData;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Contains event data for item tracking events.
+///
+public class ItemTrackedEventArgs : TrackerEventArgs
+{
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ /// The item that was tracked or untracked
+ ///
+ /// The name of the item that was tracked.
+ ///
+ /// The speech recognition confidence.
+ /// If the item was auto tracked
+ public ItemTrackedEventArgs(Item? item, string? trackedAs, float? confidence, bool autoTracked)
+ : base(confidence, autoTracked)
+ {
+ Item = item;
+ TrackedAs = trackedAs;
+ }
+
+ ///
+ /// Gets the name of the item as it was tracked.
+ ///
+ public string? TrackedAs { get; }
+
+ ///
+ /// The item that was tracked or untracked
+ ///
+ public Item? Item { get; }
+}
diff --git a/src/Randomizer.Data/Tracking/LocationClearedEventArgs.cs b/src/Randomizer.Data/Tracking/LocationClearedEventArgs.cs
new file mode 100644
index 000000000..b2e06cfa2
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/LocationClearedEventArgs.cs
@@ -0,0 +1,27 @@
+using Randomizer.Data.WorldData;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Provides data for events that occur when clearing a location.
+///
+public class LocationClearedEventArgs : TrackerEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The location that was cleared.
+ /// The speech recognition confidence.
+ /// If the location was automatically tracked
+ public LocationClearedEventArgs(Location location, float? confidence, bool autoTracked)
+ : base(confidence, autoTracked)
+ {
+ Location = location;
+ }
+
+ ///
+ /// Gets the location that was cleared.
+ ///
+ public Location Location { get; }
+}
diff --git a/src/Randomizer.SMZ3.Tracking/TrackChangedEventArgs.cs b/src/Randomizer.Data/Tracking/TrackChangedEventArgs.cs
similarity index 91%
rename from src/Randomizer.SMZ3.Tracking/TrackChangedEventArgs.cs
rename to src/Randomizer.Data/Tracking/TrackChangedEventArgs.cs
index fbdbe7ae8..dbded79a6 100644
--- a/src/Randomizer.SMZ3.Tracking/TrackChangedEventArgs.cs
+++ b/src/Randomizer.Data/Tracking/TrackChangedEventArgs.cs
@@ -1,7 +1,7 @@
using System;
using MSURandomizerLibrary.Configs;
-namespace Randomizer.SMZ3.Tracking;
+namespace Randomizer.Data.Tracking;
public class TrackChangedEventArgs : EventArgs
{
diff --git a/src/Randomizer.SMZ3.Tracking/TrackNumberEventArgs.cs b/src/Randomizer.Data/Tracking/TrackNumberEventArgs.cs
similarity index 85%
rename from src/Randomizer.SMZ3.Tracking/TrackNumberEventArgs.cs
rename to src/Randomizer.Data/Tracking/TrackNumberEventArgs.cs
index 398f45d62..644443c27 100644
--- a/src/Randomizer.SMZ3.Tracking/TrackNumberEventArgs.cs
+++ b/src/Randomizer.Data/Tracking/TrackNumberEventArgs.cs
@@ -1,6 +1,6 @@
using System;
-namespace Randomizer.SMZ3.Tracking;
+namespace Randomizer.Data.Tracking;
public class TrackNumberEventArgs : EventArgs
{
diff --git a/src/Randomizer.Data/Tracking/TrackerEventArgs.cs b/src/Randomizer.Data/Tracking/TrackerEventArgs.cs
new file mode 100644
index 000000000..3e1b67f6f
--- /dev/null
+++ b/src/Randomizer.Data/Tracking/TrackerEventArgs.cs
@@ -0,0 +1,60 @@
+using System;
+
+namespace Randomizer.Data.Tracking;
+
+///
+/// Contains event data for tracking events.
+///
+public class TrackerEventArgs : EventArgs
+{
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ /// The speech recognition confidence.
+ /// If the event was triggered by auto tracker
+ public TrackerEventArgs(float? confidence, bool autoTracked = false)
+ {
+ Confidence = confidence;
+ AutoTracked = autoTracked;
+ }
+
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ /// The speech recognition confidence.
+ /// The phrase that was recognized.
+ public TrackerEventArgs(float? confidence, string? phrase)
+ {
+ Confidence = confidence;
+ Phrase = phrase;
+ }
+
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ /// If the event was triggered by auto tracker
+ public TrackerEventArgs(bool autoTracked)
+ {
+ AutoTracked = autoTracked;
+ }
+
+ ///
+ /// Gets the speech recognition confidence as a value between 0.0 and
+ /// 1.0, or null if the event was not initiated by speech
+ /// recognition.
+ ///
+ public float? Confidence { get; }
+
+ ///
+ /// Gets the phrase Tracker recognized, or null.
+ ///
+ public string? Phrase { get; }
+
+ ///
+ /// If the event was triggered by auto tracker
+ ///
+ public bool AutoTracked { get; init; }
+}
diff --git a/src/Randomizer.PatchBuilder/PatchBuilderService.cs b/src/Randomizer.PatchBuilder/PatchBuilderService.cs
index 7692748b7..346339279 100644
--- a/src/Randomizer.PatchBuilder/PatchBuilderService.cs
+++ b/src/Randomizer.PatchBuilder/PatchBuilderService.cs
@@ -4,6 +4,7 @@
using MSURandomizerLibrary.Services;
using Randomizer.Data.Options;
using Randomizer.SMZ3.Generation;
+using Randomizer.SMZ3.Infrastructure;
namespace Randomizer.PatchBuilder;
@@ -15,10 +16,11 @@ public class PatchBuilderService
private readonly IMsuTypeService _msuTypeService;
private readonly IMsuLookupService _msuLookupService;
private readonly IMsuSelectorService _msuSelectorService;
+ private readonly RomLauncherService _romLauncherService;
private readonly string _solutionPath;
private readonly string _randomizerRomPath;
- public PatchBuilderService(ILogger logger, RomGenerationService romGenerationService, OptionsFactory optionsFactory, IMsuLookupService msuLookupService, IMsuSelectorService msuSelectorService, IMsuTypeService msuTypeService)
+ public PatchBuilderService(ILogger logger, RomGenerationService romGenerationService, OptionsFactory optionsFactory, IMsuLookupService msuLookupService, IMsuSelectorService msuSelectorService, IMsuTypeService msuTypeService, RomLauncherService romLauncherService)
{
_logger = logger;
_romGenerationService = romGenerationService;
@@ -28,6 +30,7 @@ public PatchBuilderService(ILogger logger, RomGenerationSer
_msuLookupService = msuLookupService;
_msuSelectorService = msuSelectorService;
_msuTypeService = msuTypeService;
+ _romLauncherService = romLauncherService;
}
public void CreatePatches(PatchBuilderConfig config)
@@ -218,42 +221,8 @@ private void Launch(PatchBuilderConfig config)
return;
}
- var launchApplication = config.EnvironmentSettings.LaunchApplication;
- var launchArguments = "";
- if (string.IsNullOrEmpty(launchApplication))
- {
- launchApplication = romPath;
- }
- else
- {
- if (string.IsNullOrEmpty(config.EnvironmentSettings.LaunchArguments))
- {
- launchArguments = $"\"{romPath}\"";
- }
- else if (config.EnvironmentSettings.LaunchArguments.Contains("%rom%"))
- {
- launchArguments = config.EnvironmentSettings.LaunchArguments.Replace("%rom%", $"{romPath}");
- }
- else
- {
- launchArguments = $"{config.EnvironmentSettings.LaunchArguments} \"{romPath}\"";
- }
- }
-
- try
- {
- _logger.LogInformation("Executing {FileName} {Arguments}", launchApplication, launchArguments);
- Process.Start(new ProcessStartInfo
- {
- FileName = launchApplication,
- Arguments = launchArguments,
- UseShellExecute = true
- });
- }
- catch (Exception e)
- {
- _logger.LogError(e, "Unable to launch rom");
- }
+ _romLauncherService.LaunchRom(romPath, config.EnvironmentSettings.LaunchApplication,
+ config.EnvironmentSettings.LaunchArguments);
}
private static string SolutionPath
diff --git a/src/Randomizer.PatchBuilder/Program.cs b/src/Randomizer.PatchBuilder/Program.cs
index 900a5480e..41a5c3e5a 100644
--- a/src/Randomizer.PatchBuilder/Program.cs
+++ b/src/Randomizer.PatchBuilder/Program.cs
@@ -11,6 +11,7 @@
using Randomizer.Data.WorldData.Regions;
using Randomizer.PatchBuilder;
using Randomizer.Shared;
+using Randomizer.SMZ3.Infrastructure;
using Randomizer.Sprites;
var serviceProvider = new ServiceCollection()
@@ -25,6 +26,7 @@
.AddSingleton()
.AddRandomizerServices()
.AddTransient()
+ .AddTransient()
.BuildServiceProvider();
var logger = serviceProvider.GetRequiredService>();
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs
index 4b0cc832a..9fb2c16ea 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTracker.cs
@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
using Randomizer.Data.WorldData.Regions;
using Randomizer.Data.WorldData.Regions.Zelda;
using Randomizer.Data.WorldData;
@@ -14,943 +15,939 @@
using Randomizer.SMZ3.Tracking.Services;
using Randomizer.SMZ3.Tracking.VoiceCommands;
using Randomizer.Data.Options;
+using Randomizer.Data.Tracking;
+using Randomizer.Shared.Enums;
-namespace Randomizer.SMZ3.Tracking.AutoTracking
+namespace Randomizer.SMZ3.Tracking.AutoTracking;
+
+///
+/// Manages the automated checking of the emulator memory for purposes of auto tracking
+/// and other things using the appropriate connector (USB2SNES or Lura) based on user
+/// preferences.
+///
+public class AutoTracker : IAutoTracker
{
+ private readonly ILogger _logger;
+ private readonly List _readActions = new();
+ private readonly Dictionary _readActionMap = new();
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly IItemService _itemService;
+ private readonly IEnumerable _zeldaStateChecks;
+ private readonly IEnumerable _metroidStateChecks;
+ private readonly TrackerModuleFactory _trackerModuleFactory;
+ private readonly IRandomizerConfigService _config;
+ private readonly IWorldService _worldService;
+ private int _currentIndex;
+ private Game _previousGame;
+ private bool _hasStarted;
+ private bool _hasValidState;
+ private IEmulatorConnector? _connector;
+ private readonly Queue _sendActions = new();
+ private CancellationTokenSource? _stopSendingMessages;
+ private int _numGTItems;
+ private bool _seenGTTorch;
+ private bool _foundGTKey;
+ private bool _beatBothBosses;
+ private string? _previousRom;
+
///
- /// Manages the automated checking of the emulator memory for purposes of auto tracking
- /// and other things using the appropriate connector (USB2SNES or Lura) based on user
- /// preferences.
+ /// Constructor for Auto Tracker
///
- public class AutoTracker
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public AutoTracker(ILogger logger,
+ ILoggerFactory loggerFactory,
+ IItemService itemService,
+ IEnumerable zeldaStateChecks,
+ IEnumerable metroidStateChecks,
+ TrackerModuleFactory trackerModuleFactory,
+ IRandomizerConfigService randomizerConfigService,
+ IWorldService worldService,
+ ITracker tracker)
{
- private readonly ILogger _logger;
- private readonly List _readActions = new();
- private readonly Dictionary _readActionMap = new();
- private readonly ILoggerFactory _loggerFactory;
- private readonly IItemService _itemService;
- private readonly IEnumerable _zeldaStateChecks;
- private readonly IEnumerable _metroidStateChecks;
- private readonly TrackerModuleFactory _trackerModuleFactory;
- private readonly IRandomizerConfigService _config;
- private readonly IWorldService _worldService;
- private int _currentIndex;
- private Game _previousGame;
- private bool _hasStarted;
- private bool _hasValidState;
- private IEmulatorConnector? _connector;
- private readonly Queue _sendActions = new();
- private CancellationTokenSource? _stopSendingMessages;
- private int _numGTItems;
- private bool _seenGTTorch;
- private bool _foundGTKey;
- private bool _beatBothBosses;
- private string? _previousRom;
-
- ///
- /// Constructor for Auto Tracker
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public AutoTracker(ILogger logger,
- ILoggerFactory loggerFactory,
- IItemService itemService,
- IEnumerable zeldaStateChecks,
- IEnumerable metroidStateChecks,
- TrackerModuleFactory trackerModuleFactory,
- IRandomizerConfigService randomizerConfigService,
- IWorldService worldService,
- Tracker tracker
- )
- {
- _logger = logger;
- _loggerFactory = loggerFactory;
- _itemService = itemService;
- _trackerModuleFactory = trackerModuleFactory;
- _config = randomizerConfigService;
- _worldService = worldService;
- Tracker = tracker;
-
- // Check if the game has started or not
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7e0020,
- Length = 0x1,
- Game = Game.Neither,
- Action = CheckStarted
- });
-
- // Check whether the player is in Zelda or Metroid
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.CartRAM,
- Address = 0xA173FE,
- Length = 0x2,
- Game = Game.Both,
- Action = CheckGame
- });
-
- // Check Zelda rooms (caves, houses, dungeons)
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7ef000,
- Length = 0x250,
- Game = Game.Zelda,
- FrequencySeconds = 1,
- Action = CheckZeldaRooms
- });
-
- // Check Zelda overworld and NPC locations and inventory
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7ef280,
- Length = 0x200,
- Game = Game.Zelda,
- FrequencySeconds = 1,
- Action = CheckZeldaMisc
- });
-
- // Check Zelda state
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7e0000,
- Length = 0x250,
- Game = Game.Zelda,
- Action = CheckZeldaState
- });
-
- // Check if Ganon is defeated
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.CartRAM,
- Address = 0xA17400,
- Length = 0x120,
- Game = Game.Both,
- FrequencySeconds = 1,
- Action = CheckBeatFinalBosses
- });
-
- // Check Metroid locations
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7ed870,
- Length = 0x20,
- Game = Game.SM,
- FrequencySeconds = 1,
- Action = CheckMetroidLocations
- });
-
- // Check Metroid bosses
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7ed828,
- Length = 0x08,
- Game = Game.SM,
- FrequencySeconds = 1,
- Action = CheckMetroidBosses
- });
-
- // Check state of if the player has entered the ship
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7e0FB2,
- Length = 0x2,
- Game = Game.SM,
- Action = CheckShip
- });
-
- // Check Metroid state
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7e0750,
- Length = 0x400,
- Game = Game.SM,
- Action = CheckMetroidState
- });
-
- // Check for current Zelda song
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E010B,
- Length = 0x01,
- Game = Game.Zelda,
- FrequencySeconds = 2,
- Action = action => Tracker.UpdateTrackNumber(action.CurrentData!.ReadUInt8(0))
- });
-
- // Check for current Metroid song
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E0332,
- Length = 0x01,
- Game = Game.SM,
- FrequencySeconds = 2,
- Action = action => Tracker.UpdateTrackNumber(action.CurrentData!.ReadUInt8(0))
- });
-
- // Check for current title screen song
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E0331,
- Length = 0x02,
- Game = Game.Neither,
- FrequencySeconds = 2,
- Action = action => Tracker.UpdateTrackNumber(action.CurrentData!.ReadUInt8(1))
- });
-
- // Get the number of items given to the player via the interactor
- AddReadAction(new()
- {
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.CartRAM,
- Address = 0xA26000,
- Length = 0x300,
- Game = Game.Both,
- FrequencySeconds = 30,
- Action = action =>
- {
- Tracker.GameService?.SyncItems(action);
- }
- });
-
- // Get the number of items given to the player via the interactor
- AddReadAction(new()
+ _logger = logger;
+ _loggerFactory = loggerFactory;
+ _itemService = itemService;
+ _trackerModuleFactory = trackerModuleFactory;
+ _config = randomizerConfigService;
+ _worldService = worldService;
+ Tracker = tracker;
+
+ // Check if the game has started or not
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7e0020,
+ Length = 0x1,
+ Game = Game.Neither,
+ Action = CheckStarted
+ });
+
+ // Check whether the player is in Zelda or Metroid
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.CartRAM,
+ Address = 0xA173FE,
+ Length = 0x2,
+ Game = Game.Both,
+ Action = CheckGame
+ });
+
+ // Check Zelda rooms (caves, houses, dungeons)
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7ef000,
+ Length = 0x250,
+ Game = Game.Zelda,
+ FrequencySeconds = 1,
+ Action = CheckZeldaRooms
+ });
+
+ // Check Zelda overworld and NPC locations and inventory
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7ef280,
+ Length = 0x200,
+ Game = Game.Zelda,
+ FrequencySeconds = 1,
+ Action = CheckZeldaMisc
+ });
+
+ // Check Zelda state
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7e0000,
+ Length = 0x250,
+ Game = Game.Zelda,
+ Action = CheckZeldaState
+ });
+
+ // Check if Ganon is defeated
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.CartRAM,
+ Address = 0xA17400,
+ Length = 0x120,
+ Game = Game.Both,
+ FrequencySeconds = 1,
+ Action = CheckBeatFinalBosses
+ });
+
+ // Check Metroid locations
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7ed870,
+ Length = 0x20,
+ Game = Game.SM,
+ FrequencySeconds = 1,
+ Action = CheckMetroidLocations
+ });
+
+ // Check Metroid bosses
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7ed828,
+ Length = 0x08,
+ Game = Game.SM,
+ FrequencySeconds = 1,
+ Action = CheckMetroidBosses
+ });
+
+ // Check state of if the player has entered the ship
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7e0FB2,
+ Length = 0x2,
+ Game = Game.SM,
+ Action = CheckShip
+ });
+
+ // Check Metroid state
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7e0750,
+ Length = 0x400,
+ Game = Game.SM,
+ Action = CheckMetroidState
+ });
+
+ // Check for current Zelda song
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E010B,
+ Length = 0x01,
+ Game = Game.Zelda,
+ FrequencySeconds = 2,
+ Action = action => Tracker.UpdateTrackNumber(action.CurrentData!.ReadUInt8(0))
+ });
+
+ // Check for current Metroid song
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E0332,
+ Length = 0x01,
+ Game = Game.SM,
+ FrequencySeconds = 2,
+ Action = action => Tracker.UpdateTrackNumber(action.CurrentData!.ReadUInt8(0))
+ });
+
+ // Check for current title screen song
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E0331,
+ Length = 0x02,
+ Game = Game.Neither,
+ FrequencySeconds = 2,
+ Action = action => Tracker.UpdateTrackNumber(action.CurrentData!.ReadUInt8(1))
+ });
+
+ // Get the number of items given to the player via the interactor
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.CartRAM,
+ Address = 0xA26000,
+ Length = 0x300,
+ Game = Game.Both,
+ FrequencySeconds = 30,
+ Action = action =>
{
- Type = EmulatorActionType.ReadBlock,
- Domain = MemoryDomain.CartRAM,
- Address = 0xA26300,
- Length = 0x300,
- Game = Game.Both,
- FrequencySeconds = 30,
- Action = action =>
- {
- Tracker.GameService?.SyncItems(action);
- }
- });
+ Tracker.GameService?.SyncItems(action);
+ }
+ });
- _zeldaStateChecks = zeldaStateChecks;
- _metroidStateChecks = metroidStateChecks;
- _logger.LogInformation("Zelda state checks: {ZeldaStateCount}", _zeldaStateChecks.Count());
- _logger.LogInformation("Metroid state checks: {MetroidStateCount}", _metroidStateChecks.Count());
- }
+ // Get the number of items given to the player via the interactor
+ AddReadAction(new()
+ {
+ Type = EmulatorActionType.ReadBlock,
+ Domain = MemoryDomain.CartRAM,
+ Address = 0xA26300,
+ Length = 0x300,
+ Game = Game.Both,
+ FrequencySeconds = 30,
+ Action = action =>
+ {
+ Tracker.GameService?.SyncItems(action);
+ }
+ });
+
+ _zeldaStateChecks = zeldaStateChecks;
+ _metroidStateChecks = metroidStateChecks;
+ _logger.LogInformation("Zelda state checks: {ZeldaStateCount}", _zeldaStateChecks.Count());
+ _logger.LogInformation("Metroid state checks: {MetroidStateCount}", _metroidStateChecks.Count());
+ }
- ///
- /// The tracker associated with this auto tracker
- ///
- public Tracker Tracker { get; set; }
+ ///
+ /// The tracker associated with this auto tracker
+ ///
+ public ITracker Tracker { get; }
- ///
- /// The type of connector that the auto tracker is currently using
- ///
- public EmulatorConnectorType ConnectorType { get; protected set; }
+ ///
+ /// The type of connector that the auto tracker is currently using
+ ///
+ public EmulatorConnectorType ConnectorType { get; private set; }
- ///
- /// The game that the player is currently in
- ///
- public Game CurrentGame { get; protected set; } = Game.Neither;
+ ///
+ /// The game that the player is currently in
+ ///
+ public Game CurrentGame { get; private set; } = Game.Neither;
- ///
- /// The latest state that the player in LTTP (location, health, etc.)
- ///
- public AutoTrackerZeldaState? ZeldaState { get; protected set; }
+ ///
+ /// The latest state that the player in LTTP (location, health, etc.)
+ ///
+ public AutoTrackerZeldaState? ZeldaState { get; private set; }
- ///
- /// The latest state that the player in Super Metroid (location, health, etc.)
- ///
- public AutoTrackerMetroidState? MetroidState { get; protected set; }
+ ///
+ /// The latest state that the player in Super Metroid (location, health, etc.)
+ ///
+ public AutoTrackerMetroidState? MetroidState { get; private set; }
- ///
- /// Disables the current connector and creates the requested type
- ///
- public void SetConnector(EmulatorConnectorType type, string? qusb2SnesIp)
+ ///
+ /// Disables the current connector and creates the requested type
+ ///
+ public void SetConnector(EmulatorConnectorType type, string? qusb2SnesIp)
+ {
+ if (_connector != null)
{
- if (_connector != null)
- {
- _connector.Dispose();
- _connector = null;
- }
+ _connector.Dispose();
+ _connector = null;
+ }
- if (type != EmulatorConnectorType.None)
+ if (type != EmulatorConnectorType.None)
+ {
+ if (type == EmulatorConnectorType.USB2SNES)
{
- if (type == EmulatorConnectorType.USB2SNES)
- {
- _connector = new USB2SNESConnector(_loggerFactory.CreateLogger(), qusb2SnesIp);
- }
- else
- {
- _connector = new LuaConnector(_loggerFactory.CreateLogger());
- }
- ConnectorType = type;
- _connector.OnConnected += Connector_Connected;
- _connector.OnDisconnected += Connector_Disconnected;
- _connector.MessageReceived += Connector_MessageReceived;
- AutoTrackerEnabled?.Invoke(this, EventArgs.Empty);
+ _connector = new USB2SNESConnector(_loggerFactory.CreateLogger(), qusb2SnesIp);
}
else
{
- ConnectorType = EmulatorConnectorType.None;
- AutoTrackerDisabled?.Invoke(this, EventArgs.Empty);
+ _connector = new LuaConnector(_loggerFactory.CreateLogger());
}
+ ConnectorType = type;
+ _connector.OnConnected += Connector_Connected;
+ _connector.OnDisconnected += Connector_Disconnected;
+ _connector.MessageReceived += Connector_MessageReceived;
+ AutoTrackerEnabled?.Invoke(this, EventArgs.Empty);
+ }
+ else
+ {
+ ConnectorType = EmulatorConnectorType.None;
+ AutoTrackerDisabled?.Invoke(this, EventArgs.Empty);
}
+ }
- ///
- /// Occurs when the tracker's auto tracker is enabled
- ///
- public event EventHandler? AutoTrackerEnabled;
-
- ///
- /// Occurs when the tracker's auto tracker is disabled
- ///
- public event EventHandler? AutoTrackerDisabled;
-
- ///
- /// Occurs when the tracker's auto tracker is connected
- ///
- public event EventHandler? AutoTrackerConnected;
-
- ///
- /// Occurs when the tracker's auto tracker is disconnected
- ///
- public event EventHandler? AutoTrackerDisconnected;
-
- ///
- /// Occurs when the MSU track number has changed
- ///
- public event EventHandler? TrackNumberUpdated;
-
- ///
- /// The action to run when the player asks Tracker to look at the game
- ///
- public AutoTrackerViewedAction? LatestViewAction;
-
- ///
- /// If a connector is currently enabled
- ///
- public bool IsEnabled => _connector != null;
-
- ///
- /// If a connector is currently connected to the emulator
- ///
- public bool IsConnected => _connector != null && _connector.IsConnected();
-
- ///
- /// If a connector is currently connected to the emulator and a valid game state is detected
- ///
- public bool HasValidState => IsConnected && _hasValidState;
-
- ///
- /// If the auto tracker is currently sending messages
- ///
- public bool IsSendingMessages { get; set; }
-
- ///
- /// If the player currently has a fairy
- ///
- public bool PlayerHasFairy { get; protected set; }
-
- ///
- /// If the user is activately in an SMZ3 rom
- ///
- public bool IsInSMZ3 => string.IsNullOrEmpty(_previousRom) || _previousRom.StartsWith("SMZ3 Cas");
-
- ///
- /// Called when the connector successfully established a connection with the emulator
- ///
- ///
- ///
- protected async void Connector_Connected(object? sender, EventArgs e)
- {
- _logger.LogInformation("Connector Connected");
- await Task.Delay(TimeSpan.FromSeconds(0.1f));
- if (!IsSendingMessages)
- {
- _logger.LogInformation("Start sending messages");
- Tracker.Say(x => x.AutoTracker.WhenConnected);
- AutoTrackerConnected?.Invoke(this, EventArgs.Empty);
- _stopSendingMessages = new CancellationTokenSource();
- _ = SendMessagesAsync(_stopSendingMessages.Token);
- _currentIndex = 0;
- }
+ ///
+ /// Occurs when the tracker's auto tracker is enabled
+ ///
+ public event EventHandler? AutoTrackerEnabled;
+
+ ///
+ /// Occurs when the tracker's auto tracker is disabled
+ ///
+ public event EventHandler? AutoTrackerDisabled;
+
+ ///
+ /// Occurs when the tracker's auto tracker is connected
+ ///
+ public event EventHandler? AutoTrackerConnected;
+
+ ///
+ /// Occurs when the tracker's auto tracker is disconnected
+ ///
+ public event EventHandler? AutoTrackerDisconnected;
+
+ ///
+ /// The action to run when the player asks Tracker to look at the game
+ ///
+ public AutoTrackerViewedAction? LatestViewAction { get; set; }
+
+ ///
+ /// If a connector is currently enabled
+ ///
+ public bool IsEnabled => _connector != null;
+
+ ///
+ /// If a connector is currently connected to the emulator
+ ///
+ public bool IsConnected => _connector != null && _connector.IsConnected();
+
+ ///
+ /// If a connector is currently connected to the emulator and a valid game state is detected
+ ///
+ public bool HasValidState => IsConnected && _hasValidState;
+
+ ///
+ /// If the auto tracker is currently sending messages
+ ///
+ public bool IsSendingMessages { get; set; }
+
+ ///
+ /// If the player currently has a fairy
+ ///
+ public bool PlayerHasFairy { get; private set; }
+
+ ///
+ /// If the user is activately in an SMZ3 rom
+ ///
+ public bool IsInSMZ3 => string.IsNullOrEmpty(_previousRom) || _previousRom.StartsWith("SMZ3 Cas");
+
+ ///
+ /// Called when the connector successfully established a connection with the emulator
+ ///
+ ///
+ ///
+ private async void Connector_Connected(object? sender, EventArgs e)
+ {
+ _logger.LogInformation("Connector Connected");
+ await Task.Delay(TimeSpan.FromSeconds(0.1f));
+ if (!IsSendingMessages)
+ {
+ _logger.LogInformation("Start sending messages");
+ Tracker.Say(x => x.AutoTracker.WhenConnected);
+ AutoTrackerConnected?.Invoke(this, EventArgs.Empty);
+ _stopSendingMessages = new CancellationTokenSource();
+ _ = SendMessagesAsync(_stopSendingMessages.Token);
+ _currentIndex = 0;
}
+ }
+
+ ///
+ /// Writes a particular action to the emulator memory
+ ///
+ /// The action to write to memory
+ public void WriteToMemory(EmulatorAction action)
+ {
+ _sendActions.Enqueue(action);
+ }
- ///
- /// Writes a particular action to the emulator memory
- ///
- /// The action to write to memory
- public void WriteToMemory(EmulatorAction action)
+ ///
+ /// Called when a connector has temporarily lost connection with the emulator
+ ///
+ ///
+ ///
+ private void Connector_Disconnected(object? sender, EventArgs e)
+ {
+ Tracker.Say(x => x.AutoTracker.WhenDisconnected);
+ _logger.LogInformation("Disconnected");
+ AutoTrackerDisconnected?.Invoke(this, EventArgs.Empty);
+ _stopSendingMessages?.Cancel();
+
+ // Reset everything once
+ IsSendingMessages = false;
+ foreach (var action in _readActions)
{
- _sendActions.Enqueue(action);
+ action.ClearData();
}
+ _sendActions.Clear();
+ CurrentGame = Game.Neither;
+ _hasValidState = false;
+ }
- ///
- /// Called when a connector has temporarily lost connection with the emulator
- ///
- ///
- ///
- protected void Connector_Disconnected(object? sender, EventArgs e)
+ ///
+ /// The connector has received memory from the emulator
+ ///
+ ///
+ ///
+ private void Connector_MessageReceived(object? sender, EmulatorDataReceivedEventArgs e)
+ {
+ // If the user is playing SMZ3 (if we don't have the name, assume that they are)
+ if (string.IsNullOrEmpty(e.RomName) || e.RomName.StartsWith("SMZ3 Cas"))
{
- Tracker.Say(x => x.AutoTracker.WhenDisconnected);
- _logger.LogInformation("Disconnected");
- AutoTrackerDisconnected?.Invoke(this, EventArgs.Empty);
- _stopSendingMessages?.Cancel();
+ if (!string.IsNullOrEmpty(_previousRom) && e.RomName != _previousRom)
+ {
+ _logger.LogInformation("Changed to SMZ3 rom {RomName} ({RomHash})", e.RomName,e.RomHash);
+ Tracker.Say(x => x.AutoTracker.SwitchedToSMZ3Rom);
+ }
- // Reset everything once
- IsSendingMessages = false;
- foreach (var action in _readActions)
+ // Verify that message we received is still valid, then execute
+ if (_readActionMap[e.Address].ShouldProcess(CurrentGame, _hasStarted))
{
- action.ClearData();
+ _readActionMap[e.Address].Invoke(e.Data);
}
- _sendActions.Clear();
- CurrentGame = Game.Neither;
- _hasValidState = false;
}
-
- ///
- /// The connector has received memory from the emulator
- ///
- ///
- ///
- protected void Connector_MessageReceived(object? sender, EmulatorDataReceivedEventArgs e)
+ // If the user is switching to a non-SMZ3 rom
+ else if (!string.IsNullOrEmpty(e.RomName) && e.RomName != _previousRom)
{
- // If the user is playing SMZ3 (if we don't have the name, assume that they are)
- if (string.IsNullOrEmpty(e.RomName) || e.RomName.StartsWith("SMZ3 Cas"))
- {
- if (!string.IsNullOrEmpty(_previousRom) && e.RomName != _previousRom)
- {
- _logger.LogInformation("Changed to SMZ3 rom {RomName} ({RomHash})", e.RomName,e.RomHash);
- Tracker.Say(x => x.AutoTracker.SwitchedToSMZ3Rom);
- }
+ _logger.LogInformation("Ignoring rom {RomName} ({RomHash})", e.RomName,e.RomHash);
- // Verify that message we received is still valid, then execute
- if (_readActionMap[e.Address].ShouldProcess(CurrentGame, _hasStarted))
- {
- _readActionMap[e.Address].Invoke(e.Data);
- }
- }
- // If the user is switching to a non-SMZ3 rom
- else if (!string.IsNullOrEmpty(e.RomName) && e.RomName != _previousRom)
+ var key = "Unknown";
+ if (Tracker.Responses.AutoTracker.SwitchedToOtherRom.ContainsKey(e.RomHash!))
{
- _logger.LogInformation("Ignoring rom {RomName} ({RomHash})", e.RomName,e.RomHash);
-
- var key = "Unknown";
- if (Tracker.Responses.AutoTracker.SwitchedToOtherRom.ContainsKey(e.RomHash!))
- {
- key = e.RomHash!;
- }
-
- Tracker.Say(x => x.AutoTracker.SwitchedToOtherRom[key]);
+ key = e.RomHash!;
}
- _previousRom = e.RomName;
+ Tracker.Say(x => x.AutoTracker.SwitchedToOtherRom[key]);
}
- ///
- /// Sends requests out to the connected lua script
- ///
- protected async Task SendMessagesAsync(CancellationToken cancellationToken)
+ _previousRom = e.RomName;
+ }
+
+ ///
+ /// Sends requests out to the connected lua script
+ ///
+ private async Task SendMessagesAsync(CancellationToken cancellationToken)
+ {
+ Thread.CurrentThread.Name = DateTime.Now.ToString(CultureInfo.InvariantCulture);
+ _logger.LogInformation("Start sending messages {ThreadName}", Thread.CurrentThread.Name);
+ IsSendingMessages = true;
+ while (_connector != null && _connector.IsConnected() && !cancellationToken.IsCancellationRequested)
{
- Thread.CurrentThread.Name = DateTime.Now.ToString(CultureInfo.InvariantCulture);
- _logger.LogInformation("Start sending messages {ThreadName}", Thread.CurrentThread.Name);
- IsSendingMessages = true;
- while (_connector != null && _connector.IsConnected() && !cancellationToken.IsCancellationRequested)
+ if (_connector.CanSendMessage())
{
- if (_connector.CanSendMessage())
+ if (_sendActions.Count > 0)
{
- if (_sendActions.Count > 0)
- {
- var nextAction = _sendActions.Dequeue();
+ var nextAction = _sendActions.Dequeue();
- if (nextAction.ShouldProcess(CurrentGame, _hasStarted))
- {
- _connector.SendMessage(nextAction);
- }
+ if (nextAction.ShouldProcess(CurrentGame, _hasStarted))
+ {
+ _connector.SendMessage(nextAction);
}
- else
+ }
+ else
+ {
+ while (!_readActions[_currentIndex].ShouldProcess(CurrentGame, _hasStarted))
{
- while (!_readActions[_currentIndex].ShouldProcess(CurrentGame, _hasStarted))
- {
- _currentIndex = (_currentIndex + 1) % _readActions.Count;
- }
- _connector.SendMessage(_readActions[_currentIndex]);
_currentIndex = (_currentIndex + 1) % _readActions.Count;
}
+ _connector.SendMessage(_readActions[_currentIndex]);
+ _currentIndex = (_currentIndex + 1) % _readActions.Count;
}
-
- await Task.Delay(TimeSpan.FromSeconds(0.1f), cancellationToken);
}
- IsSendingMessages = false;
- _logger.LogInformation("Stop sending messages {ThreadName}", Thread.CurrentThread.Name);
- }
- ///
- /// Adds a read action to repeatedly call out to the emulator
- ///
- ///
- protected void AddReadAction(EmulatorAction action)
- {
- _readActions.Add(action);
- _readActionMap.Add(action.Address, action);
+ await Task.Delay(TimeSpan.FromSeconds(0.1f), cancellationToken);
}
+ IsSendingMessages = false;
+ _logger.LogInformation("Stop sending messages {ThreadName}", Thread.CurrentThread.Name);
+ }
- ///
- /// Check if the player has started playing the game
- ///
- ///
- protected void CheckStarted(EmulatorAction action)
- {
- if (action.CurrentData == null) return;
- var value = action.CurrentData.ReadUInt8(0);
- if (value != 0 && !_hasStarted)
- {
- _logger.LogInformation("Game started");
- _hasStarted = true;
-
- if (Tracker.World.Config.MultiWorld && _worldService.Worlds.Count > 1)
- {
- 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, worldCount, otherPlayerName);
- }
- else
- {
- Tracker.Say(x => x.AutoTracker.GameStarted, Tracker.Rom?.Seed);
- }
- }
- }
+ ///
+ /// Adds a read action to repeatedly call out to the emulator
+ ///
+ ///
+ private void AddReadAction(EmulatorAction action)
+ {
+ _readActions.Add(action);
+ _readActionMap.Add(action.Address, action);
+ }
- ///
- /// Checks which game the player is currently in
- ///
- ///
- protected void CheckGame(EmulatorAction action)
+ ///
+ /// Check if the player has started playing the game
+ ///
+ ///
+ private void CheckStarted(EmulatorAction action)
+ {
+ if (action.CurrentData == null) return;
+ var value = action.CurrentData.ReadUInt8(0);
+ if (value != 0 && !_hasStarted)
{
- if (action.CurrentData == null) return;
- _previousGame = CurrentGame;
- var value = action.CurrentData.ReadUInt8(0);
- if (value == 0x00)
- {
- CurrentGame = Game.Zelda;
- _hasValidState = true;
- }
- else if (value == 0xFF)
- {
- CurrentGame = Game.SM;
- }
- else if (value == 0x11)
+ _logger.LogInformation("Game started");
+ _hasStarted = true;
+
+ if (Tracker.World.Config.MultiWorld && _worldService.Worlds.Count > 1)
{
- CurrentGame = Game.Credits;
- Tracker.UpdateTrackNumber(99);
+ 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, worldCount, otherPlayerName);
}
- if (_previousGame != CurrentGame)
+ else
{
- _logger.LogInformation("Game changed to: {CurrentGame}", CurrentGame);
+ Tracker.Say(x => x.AutoTracker.GameStarted, Tracker.Rom?.Seed);
}
}
+ }
- ///
- /// Checks if the player has cleared Zelda room locations (cave, houses, dungeons)
- /// This also checks if the player has gotten the dungeon rewards
- ///
- ///
- protected void CheckZeldaRooms(EmulatorAction action)
+ ///
+ /// Checks which game the player is currently in
+ ///
+ ///
+ private void CheckGame(EmulatorAction action)
+ {
+ if (action.CurrentData == null) return;
+ _previousGame = CurrentGame;
+ var value = action.CurrentData.ReadUInt8(0);
+ if (value == 0x00)
{
- if (!_hasValidState) return;
- if (action.CurrentData == null || action.PreviousData == null) return;
- CheckLocations(action, LocationMemoryType.Default, true, Game.Zelda);
- CheckDungeons(action.CurrentData, action.PreviousData);
+ CurrentGame = Game.Zelda;
+ _hasValidState = true;
}
-
- ///
- /// Checks if the player has cleared misc Zelda locations (overworld, NPCs)
- /// Also where you can check inventory (inventory starts at an offset of 0x80)
- ///
- ///
- protected void CheckZeldaMisc(EmulatorAction action)
+ else if (value == 0xFF)
{
- if (!_hasValidState) return;
- if (action.CurrentData == null || action.PreviousData == null) return;
- // Failsafe to prevent incorrect checking
- if (action.CurrentData?.ReadUInt8(0x190) == 0xFF && action.CurrentData?.ReadUInt8(0x191) == 0xFF)
- {
- _logger.LogInformation("Ignoring due to transition");
- return;
- }
+ CurrentGame = Game.SM;
+ }
+ else if (value == 0x11)
+ {
+ CurrentGame = Game.Credits;
+ Tracker.UpdateTrackNumber(99);
+ }
+ if (_previousGame != CurrentGame)
+ {
+ _logger.LogInformation("Game changed to: {CurrentGame}", CurrentGame);
+ }
+ }
- CheckLocations(action, LocationMemoryType.ZeldaMisc, false, Game.Zelda);
+ ///
+ /// Checks if the player has cleared Zelda room locations (cave, houses, dungeons)
+ /// This also checks if the player has gotten the dungeon rewards
+ ///
+ ///
+ private void CheckZeldaRooms(EmulatorAction action)
+ {
+ if (!_hasValidState) return;
+ if (action.CurrentData == null || action.PreviousData == null) return;
+ CheckLocations(action, LocationMemoryType.Default, true, Game.Zelda);
+ CheckDungeons(action.CurrentData, action.PreviousData);
+ }
- PlayerHasFairy = false;
- for (var i = 0; i < 4; i++)
- {
- PlayerHasFairy |= action.CurrentData?.ReadUInt8(0xDC + i) == 6;
- }
+ ///
+ /// Checks if the player has cleared misc Zelda locations (overworld, NPCs)
+ /// Also where you can check inventory (inventory starts at an offset of 0x80)
+ ///
+ ///
+ private void CheckZeldaMisc(EmulatorAction action)
+ {
+ if (!_hasValidState) return;
+ if (action.CurrentData == null || action.PreviousData == null) return;
+ // Failsafe to prevent incorrect checking
+ if (action.CurrentData?.ReadUInt8(0x190) == 0xFF && action.CurrentData?.ReadUInt8(0x191) == 0xFF)
+ {
+ _logger.LogInformation("Ignoring due to transition");
+ return;
+ }
- // Activated flute
- if (action.CurrentData?.CheckBinary8Bit(0x10C, 0x01) == true && action.PreviousData?.CheckBinary8Bit(0x10C, 0x01) != true)
- {
- var duckItem = _itemService.FirstOrDefault("Duck");
- if (duckItem?.State.TrackingState == 0)
- {
- Tracker.TrackItem(duckItem, null, null, false, true);
- }
- }
+ CheckLocations(action, LocationMemoryType.ZeldaMisc, false, Game.Zelda);
- // Check if the player cleared Aga
- if (action.CurrentData?.ReadUInt8(0x145) >= 3)
- {
- var castleTower = Tracker.World.CastleTower;
- if (castleTower.DungeonState.Cleared == false)
- {
- Tracker.MarkDungeonAsCleared(castleTower, null, autoTracked: true);
- _logger.LogInformation("Auto tracked {Name} as cleared", castleTower.Name);
- }
- }
+ PlayerHasFairy = false;
+ for (var i = 0; i < 4; i++)
+ {
+ PlayerHasFairy |= action.CurrentData?.ReadUInt8(0xDC + i) == 6;
}
- ///
- /// Checks to see if the player has cleared locations in Super Metroid
- ///
- ///
- protected void CheckMetroidLocations(EmulatorAction action)
+ // Activated flute
+ if (action.CurrentData?.CheckBinary8Bit(0x10C, 0x01) == true && action.PreviousData?.CheckBinary8Bit(0x10C, 0x01) != true)
{
- if (!_hasValidState) return;
- if (action.CurrentData != null && action.PreviousData != null)
+ var duckItem = _itemService.FirstOrDefault("Duck");
+ if (duckItem?.State.TrackingState == 0)
{
- CheckLocations(action, LocationMemoryType.Default, false, Game.SM);
+ Tracker.TrackItem(duckItem, null, null, false, true);
}
}
- ///
- /// Checks if the player has defeated Super Metroid bosses
- ///
- ///
- protected void CheckMetroidBosses(EmulatorAction action)
+ // Check if the player cleared Aga
+ if (action.CurrentData?.ReadUInt8(0x145) >= 3)
{
- if (!_hasValidState) return;
- if (action.CurrentData != null && action.PreviousData != null)
+ var castleTower = Tracker.World.CastleTower;
+ if (castleTower.DungeonState.Cleared == false)
{
- CheckSMBosses(action.CurrentData);
+ Tracker.MarkDungeonAsCleared(castleTower, null, autoTracked: true);
+ _logger.LogInformation("Auto tracked {Name} as cleared", castleTower.Name);
}
}
+ }
+
+ ///
+ /// Checks to see if the player has cleared locations in Super Metroid
+ ///
+ ///
+ private void CheckMetroidLocations(EmulatorAction action)
+ {
+ if (!_hasValidState) return;
+ if (action.CurrentData != null && action.PreviousData != null)
+ {
+ CheckLocations(action, LocationMemoryType.Default, false, Game.SM);
+ }
+ }
- ///
- /// Checks locations to see if they have accessed or not
- ///
- /// The emulator action with the emulator memory data
- /// The type of location to find the correct LocationInfo objects
- /// Set to true if this is a 16 bit value or false for 8 bit
- /// The game that is being checked
- protected void CheckLocations(EmulatorAction action, LocationMemoryType type, bool is16Bit, Game game)
+ ///
+ /// Checks if the player has defeated Super Metroid bosses
+ ///
+ ///
+ private void CheckMetroidBosses(EmulatorAction action)
+ {
+ if (!_hasValidState) return;
+ if (action.CurrentData != null && action.PreviousData != null)
{
- var currentData = action.CurrentData;
- var prevData = action.PreviousData;
+ CheckSMBosses(action.CurrentData);
+ }
+ }
+
+ ///
+ /// Checks locations to see if they have accessed or not
+ ///
+ /// The emulator action with the emulator memory data
+ /// The type of location to find the correct LocationInfo objects
+ /// Set to true if this is a 16 bit value or false for 8 bit
+ /// The game that is being checked
+ private void CheckLocations(EmulatorAction action, LocationMemoryType type, bool is16Bit, Game game)
+ {
+ var currentData = action.CurrentData;
+ var prevData = action.PreviousData;
- if (currentData == null || prevData == null) return;
+ if (currentData == null || prevData == null) return;
- // Store the locations for this action so that we don't need to grab them each time every half a second or so
- action.Locations ??= _worldService.AllLocations().Where(x =>
- x.MemoryType == type && ((game == Game.SM && (int)x.Id < 256) || (game == Game.Zelda && (int)x.Id >= 256))).ToList();
+ // Store the locations for this action so that we don't need to grab them each time every half a second or so
+ action.Locations ??= _worldService.AllLocations().Where(x =>
+ x.MemoryType == type && ((game == Game.SM && (int)x.Id < 256) || (game == Game.Zelda && (int)x.Id >= 256))).ToList();
- foreach (var location in action.Locations)
+ foreach (var location in action.Locations)
+ {
+ try
{
- try
+ var loc = location.MemoryAddress ?? 0;
+ var flag = location.MemoryFlag ?? 0;
+ var currentCleared = (is16Bit && currentData.CheckUInt16(loc * 2, flag)) || (!is16Bit && currentData.CheckBinary8Bit(loc, flag));
+ var prevCleared = (is16Bit && prevData.CheckUInt16(loc * 2, flag)) || (!is16Bit && prevData.CheckBinary8Bit(loc, flag));
+ if (location.State.Autotracked == false && currentCleared && prevCleared)
{
- var loc = location.MemoryAddress ?? 0;
- var flag = location.MemoryFlag ?? 0;
- var currentCleared = (is16Bit && currentData.CheckUInt16(loc * 2, flag)) || (!is16Bit && currentData.CheckBinary8Bit(loc, flag));
- var prevCleared = (is16Bit && prevData.CheckUInt16(loc * 2, flag)) || (!is16Bit && prevData.CheckBinary8Bit(loc, flag));
- if (location.State.Autotracked == false && currentCleared && prevCleared)
+ // Increment GT guessing game number
+ if (location.Region is GanonsTower gt && location != gt.BobsTorch)
{
- // Increment GT guessing game number
- if (location.Region is GanonsTower gt && location != gt.BobsTorch)
- {
- IncrementGTItems(location);
- }
-
- var item = location.Item;
- location.State.Autotracked = true;
- Tracker.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);
-
- // Mark HC as cleared if this was Zelda's Cell
- if (location.Id == LocationId.HyruleCastleZeldasCell && Tracker.World.HyruleCastle.DungeonState.Cleared == false)
- {
- Tracker.MarkDungeonAsCleared(Tracker.World.HyruleCastle, null, autoTracked: true);
- }
+ IncrementGTItems(location);
}
+ var item = location.Item;
+ location.State.Autotracked = true;
+ Tracker.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);
+
+ // Mark HC as cleared if this was Zelda's Cell
+ if (location.Id == LocationId.HyruleCastleZeldasCell && Tracker.World.HyruleCastle.DungeonState.Cleared == false)
+ {
+ Tracker.MarkDungeonAsCleared(Tracker.World.HyruleCastle, null, autoTracked: true);
+ }
}
- catch (Exception e)
- {
- _logger.LogError(e, "Unable to auto track location: {LocationName}", location.Name);
- Tracker.Error();
- }
+
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Unable to auto track location: {LocationName}", location.Name);
+ Tracker.Error();
}
}
+ }
- ///
- /// Checks the status of if dungeons have been cleared
- ///
- /// The latest memory data returned from the emulator
- /// The previous memory data returned from the emulator
- protected void CheckDungeons(EmulatorMemoryData currentData, EmulatorMemoryData prevData)
+ ///
+ /// Checks the status of if dungeons have been cleared
+ ///
+ /// The latest memory data returned from the emulator
+ /// The previous memory data returned from the emulator
+ private void CheckDungeons(EmulatorMemoryData currentData, EmulatorMemoryData prevData)
+ {
+ foreach (var dungeon in Tracker.World.Dungeons)
{
- foreach (var dungeon in Tracker.World.Dungeons)
+ var region = (Z3Region)dungeon;
+
+ // Skip if we don't have any memory addresses saved for this dungeon
+ if (region.MemoryAddress == null || region.MemoryFlag == null)
{
- var region = (Z3Region)dungeon;
+ continue;
+ }
- // Skip if we don't have any memory addresses saved for this dungeon
- if (region.MemoryAddress == null || region.MemoryFlag == null)
+ try
+ {
+ var prevValue = prevData.CheckUInt16((int)(region.MemoryAddress * 2), region.MemoryFlag ?? 0);
+ var currentValue = currentData.CheckUInt16((int)(region.MemoryAddress * 2), region.MemoryFlag ?? 0);
+ if (dungeon.DungeonState.AutoTracked == false && prevValue && currentValue)
{
- continue;
+ dungeon.DungeonState.AutoTracked = true;
+ Tracker.MarkDungeonAsCleared(dungeon, autoTracked: true);
+ _logger.LogInformation("Auto tracked {DungeonName} as cleared", dungeon.DungeonName);
}
- try
- {
- var prevValue = prevData.CheckUInt16((int)(region.MemoryAddress * 2), region.MemoryFlag ?? 0);
- var currentValue = currentData.CheckUInt16((int)(region.MemoryAddress * 2), region.MemoryFlag ?? 0);
- if (dungeon.DungeonState.AutoTracked == false && prevValue && currentValue)
- {
- dungeon.DungeonState.AutoTracked = true;
- Tracker.MarkDungeonAsCleared(dungeon, autoTracked: true);
- _logger.LogInformation("Auto tracked {DungeonName} as cleared", dungeon.DungeonName);
- }
-
- }
- catch (Exception e)
- {
- _logger.LogError(e, "Unable to auto track Dungeon: {DungeonName}", dungeon.DungeonName);
- Tracker.Error();
- }
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Unable to auto track Dungeon: {DungeonName}", dungeon.DungeonName);
+ Tracker.Error();
}
}
+ }
- ///
- /// Checks the status of if the Super Metroid bosses have been defeated
- ///
- /// The response from the lua script
- protected void CheckSMBosses(EmulatorMemoryData data)
+ ///
+ /// Checks the status of if the Super Metroid bosses have been defeated
+ ///
+ /// The response from the lua script
+ private void CheckSMBosses(EmulatorMemoryData data)
+ {
+ foreach (var boss in Tracker.World.AllBosses.Where(x => x.Metadata.MemoryAddress != null && x.Metadata.MemoryFlag > 0 && !x.State.AutoTracked))
{
- foreach (var boss in Tracker.World.AllBosses.Where(x => x.Metadata.MemoryAddress != null && x.Metadata.MemoryFlag > 0 && !x.State.AutoTracked))
+ if (data.CheckBinary8Bit(boss.Metadata.MemoryAddress ?? 0, boss.Metadata.MemoryFlag ?? 100))
{
- if (data.CheckBinary8Bit(boss.Metadata.MemoryAddress ?? 0, boss.Metadata.MemoryFlag ?? 100))
- {
- boss.State.AutoTracked = true;
- Tracker.MarkBossAsDefeated(boss, true, null, true);
- _logger.LogInformation("Auto tracked {BossName} as defeated", boss.Name);
- }
+ boss.State.AutoTracked = true;
+ Tracker.MarkBossAsDefeated(boss, true, null, true);
+ _logger.LogInformation("Auto tracked {BossName} as defeated", boss.Name);
}
}
+ }
+
+ ///
+ /// Tracks the current memory state of LttP for Tracker voice lines
+ ///
+ /// The message from the emulator with the memory state
+ private void CheckZeldaState(EmulatorAction action)
+ {
+ if (_previousGame != CurrentGame || action.CurrentData == null) return;
+ var prevState = ZeldaState;
+ ZeldaState = new(action.CurrentData);
+ _logger.LogDebug("{StateDetails}", ZeldaState.ToString());
+ if (prevState == null) return;
+
+ if (!_seenGTTorch
+ && ZeldaState.CurrentRoom == 140
+ && !ZeldaState.IsOnBottomHalfOfRoom
+ && !ZeldaState.IsOnRightHalfOfRoom
+ && ZeldaState.Substate != 14)
+ {
+ _seenGTTorch = true;
+ IncrementGTItems(Tracker.World.GanonsTower.BobsTorch);
+ }
- ///
- /// Tracks the current memory state of LttP for Tracker voice lines
- ///
- /// The message from the emulator with the memory state
- protected void CheckZeldaState(EmulatorAction action)
+ // Entered the triforce room
+ if (ZeldaState.State == 0x19)
{
- if (_previousGame != CurrentGame || action.CurrentData == null) return;
- var prevState = ZeldaState;
- ZeldaState = new(action.CurrentData);
- _logger.LogDebug("{StateDetails}", ZeldaState.ToString());
- if (prevState == null) return;
+ if (_beatBothBosses)
+ {
+ Tracker.GameBeaten(true);
+ }
+ }
- if (!_seenGTTorch
- && ZeldaState.CurrentRoom == 140
- && !ZeldaState.IsOnBottomHalfOfRoom
- && !ZeldaState.IsOnRightHalfOfRoom
- && ZeldaState.Substate != 14)
+ foreach (var check in _zeldaStateChecks)
+ {
+ if (check != null && check.ExecuteCheck(Tracker, ZeldaState, prevState))
{
- _seenGTTorch = true;
- IncrementGTItems(Tracker.World.GanonsTower.BobsTorch);
+ _logger.LogInformation("{StateName} detected", check.GetType().Name);
}
+ }
+ }
+
+ ///
+ /// Checks if the final bosses of both games are defeated
+ /// It appears as if 0x2 represents the first boss defeated and 0x106 is the second,
+ /// no matter what order the bosses were defeated in
+ ///
+ /// The message from the emulator with the memory state
+ private void CheckBeatFinalBosses(EmulatorAction action)
+ {
+ if (_previousGame != CurrentGame || action.CurrentData == null) return;
+ var didUpdate = false;
- // Entered the triforce room
- if (ZeldaState.State == 0x19)
+ if (action.PreviousData?.ReadUInt8(0x2) == 0 && action.CurrentData.ReadUInt8(0x2) > 0)
+ {
+ if (CurrentGame == Game.Zelda)
{
- if (_beatBothBosses)
+ var gt = Tracker.World.GanonsTower;
+ if (gt.DungeonState.Cleared == false)
{
- Tracker.GameBeaten(true);
+ _logger.LogInformation("Auto tracked Ganon's Tower");
+ Tracker.MarkDungeonAsCleared(gt, confidence: null, autoTracked: true);
+ didUpdate = true;
}
}
-
- foreach (var check in _zeldaStateChecks)
+ else if (CurrentGame == Game.SM)
{
- if (check != null && check.ExecuteCheck(Tracker, ZeldaState, prevState))
+ var motherBrain = Tracker.World.AllBosses.First(x => x.Name == "Mother Brain");
+ if (motherBrain.State.Defeated != true)
{
- _logger.LogInformation("{StateName} detected", check.GetType().Name);
+ _logger.LogInformation("Auto tracked Mother Brain");
+ Tracker.MarkBossAsDefeated(motherBrain, admittedGuilt: true, confidence: null, autoTracked: true);
+ didUpdate = true;
}
}
}
- ///
- /// Checks if the final bosses of both games are defeated
- /// It appears as if 0x2 represents the first boss defeated and 0x106 is the second,
- /// no matter what order the bosses were defeated in
- ///
- /// The message from the emulator with the memory state
- protected void CheckBeatFinalBosses(EmulatorAction action)
+ if (action.PreviousData?.ReadUInt8(0x106) == 0 && action.CurrentData.ReadUInt8(0x106) > 0)
{
- if (_previousGame != CurrentGame || action.CurrentData == null) return;
- var didUpdate = false;
-
- if (action.PreviousData?.ReadUInt8(0x2) == 0 && action.CurrentData.ReadUInt8(0x2) > 0)
+ if (CurrentGame == Game.Zelda)
{
- if (CurrentGame == Game.Zelda)
- {
- 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 (CurrentGame == Game.SM)
+ var gt = Tracker.World.GanonsTower;
+ if (gt.DungeonState.Cleared == false)
{
- 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);
- didUpdate = true;
- }
+ _logger.LogInformation("Auto tracked Ganon's Tower");
+ Tracker.MarkDungeonAsCleared(gt, confidence: null, autoTracked: true);
+ didUpdate = true;
}
}
-
- if (action.PreviousData?.ReadUInt8(0x106) == 0 && action.CurrentData.ReadUInt8(0x106) > 0)
+ else if (CurrentGame == Game.SM)
{
- if (CurrentGame == Game.Zelda)
+ var motherBrain = Tracker.World.AllBosses.First(x => x.Name == "Mother Brain");
+ if (motherBrain.State.Defeated != true)
{
- 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 Mother Brain");
+ Tracker.MarkBossAsDefeated(motherBrain, admittedGuilt: true, confidence: null, autoTracked: true);
+ didUpdate = true;
}
- else if (CurrentGame == Game.SM)
- {
- 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);
- didUpdate = true;
- }
- }
- }
-
- if (didUpdate && action.CurrentData.ReadUInt8(0x2) > 0 && action.CurrentData.ReadUInt8(0x106) > 0)
- {
- _beatBothBosses = true;
}
-
}
- ///
- /// Tracks the current memory state of SM for Tracker voice lines
- ///
- /// The message from the emulator with the memory state
- protected void CheckMetroidState(EmulatorAction action)
+ if (didUpdate && action.CurrentData.ReadUInt8(0x2) > 0 && action.CurrentData.ReadUInt8(0x106) > 0)
{
- if (_previousGame != CurrentGame || action.CurrentData == null) return;
- var prevState = MetroidState;
- MetroidState = new(action.CurrentData);
- _logger.LogDebug("{StateDetails}", MetroidState.ToString());
- if (prevState == null) return;
-
- // If the game hasn't booted up, wait until we find valid data in the Metroid state before we start
- // checking locations
- if (_hasValidState != MetroidState.IsValid)
- {
- _hasValidState = MetroidState.IsValid;
- if (_hasValidState)
- {
- _logger.LogInformation("Valid game state detected");
- }
- }
+ _beatBothBosses = true;
+ }
- if (!_hasValidState) return;
+ }
- foreach (var check in _metroidStateChecks)
+ ///
+ /// Tracks the current memory state of SM for Tracker voice lines
+ ///
+ /// The message from the emulator with the memory state
+ private void CheckMetroidState(EmulatorAction action)
+ {
+ if (_previousGame != CurrentGame || action.CurrentData == null) return;
+ var prevState = MetroidState;
+ MetroidState = new(action.CurrentData);
+ _logger.LogDebug("{StateDetails}", MetroidState.ToString());
+ if (prevState == null) return;
+
+ // If the game hasn't booted up, wait until we find valid data in the Metroid state before we start
+ // checking locations
+ if (_hasValidState != MetroidState.IsValid)
+ {
+ _hasValidState = MetroidState.IsValid;
+ if (_hasValidState)
{
- if (check != null && check.ExecuteCheck(Tracker, MetroidState, prevState))
- {
- _logger.LogInformation("{StateName} detected", check.GetType().Name);
- }
+ _logger.LogInformation("Valid game state detected");
}
}
- ///
- /// Checks if the player has entered the ship
- ///
- ///
- protected void CheckShip(EmulatorAction action)
+ if (!_hasValidState) return;
+
+ foreach (var check in _metroidStateChecks)
{
- if (_previousGame != CurrentGame || action.CurrentData == null || action.PreviousData == null) return;
- var currentInShip = action.CurrentData.ReadUInt16(0) == 0xAA4F;
- if (currentInShip && _beatBothBosses)
+ if (check != null && check.ExecuteCheck(Tracker, MetroidState, prevState))
{
- Tracker.GameBeaten(true);
+ _logger.LogInformation("{StateName} detected", check.GetType().Name);
}
}
+ }
- private void IncrementGTItems(Location location)
+ ///
+ /// Checks if the player has entered the ship
+ ///
+ ///
+ private void CheckShip(EmulatorAction action)
+ {
+ if (_previousGame != CurrentGame || action.CurrentData == null || action.PreviousData == null) return;
+ var currentInShip = action.CurrentData.ReadUInt16(0) == 0xAA4F;
+ if (currentInShip && _beatBothBosses)
{
- if (_foundGTKey || _config.Config.ZeldaKeysanity) return;
+ Tracker.GameBeaten(true);
+ }
+ }
- var chatIntegrationModule = _trackerModuleFactory.GetModule();
- _numGTItems++;
- Tracker.Say(_numGTItems.ToString());
- if (location.Item.Type == ItemType.BigKeyGT)
+ private void IncrementGTItems(Location location)
+ {
+ if (_foundGTKey || _config.Config.ZeldaKeysanity) return;
+
+ var chatIntegrationModule = _trackerModuleFactory.GetModule();
+ _numGTItems++;
+ Tracker.Say(_numGTItems.ToString());
+ if (location.Item.Type == ItemType.BigKeyGT)
+ {
+ var responseIndex = 1;
+ for (var i = 1; i <= _numGTItems; i++)
{
- var responseIndex = 1;
- for (var i = 1; i <= _numGTItems; i++)
+ if (Tracker.Responses.AutoTracker.GTKeyResponses.ContainsKey(i))
{
- if (Tracker.Responses.AutoTracker.GTKeyResponses.ContainsKey(i))
- {
- responseIndex = i;
- }
+ responseIndex = i;
}
- Tracker.Say(x => x.AutoTracker.GTKeyResponses[responseIndex], _numGTItems);
- chatIntegrationModule?.GTItemTracked(_numGTItems, true);
- _foundGTKey = true;
- }
- else
- {
- chatIntegrationModule?.GTItemTracked(_numGTItems, false);
}
+ Tracker.Say(x => x.AutoTracker.GTKeyResponses[responseIndex], _numGTItems);
+ chatIntegrationModule?.GTItemTracked(_numGTItems, true);
+ _foundGTKey = true;
+ }
+ else
+ {
+ chatIntegrationModule?.GTItemTracked(_numGTItems, false);
}
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerMetroidState.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerMetroidState.cs
deleted file mode 100644
index 53dc95672..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerMetroidState.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// Used to retrieve certain states based on the memory in Metroid
- /// Seee https://jathys.zophar.net/supermetroid/kejardon/RAMMap.txt for details on the memory
- ///
- public class AutoTrackerMetroidState
- {
- private readonly EmulatorMemoryData _data;
-
- ///
- /// Constructor
- ///
- ///
- public AutoTrackerMetroidState(EmulatorMemoryData data)
- {
- _data = data;
- }
-
- ///
- /// The overall room number the player is in
- ///
- public int CurrentRoom => _data.ReadUInt8(0x7E079B - 0x7E0750);
-
- ///
- /// The region room number the player is in
- ///
- public int CurrentRoomInRegion => _data.ReadUInt8(0x7E079D - 0x7E0750);
-
- ///
- /// The current region the player is in
- ///
- public int CurrentRegion => _data.ReadUInt8(0x7E079F - 0x7E0750);
-
- ///
- /// The amount of energy/health
- ///
- public int Energy => _data.ReadUInt16(0x7E09C2 - 0x7E0750);
-
- ///
- /// The amount currently in reserve tanks
- ///
- public int ReserveTanks => _data.ReadUInt16(0x7E09D6 - 0x7E0750);
-
- ///
- /// The max of health
- ///
- public int MaxEnergy => _data.ReadUInt16(0x7E09C4 - 0x7E0750);
-
- ///
- /// The max in reserve tanks
- ///
- public int MaxReserveTanks => _data.ReadUInt16(0x7E09D4 - 0x7E0750);
-
- ///
- /// Samus's X Location
- ///
- public int SamusX => _data.ReadUInt16(0x7E0AF6 - 0x7E0750);
-
- ///
- /// Samus's Y Location
- ///
- public int SamusY => _data.ReadUInt16(0x7E0AFA - 0x7E0750);
-
- ///
- /// Samus's current super missile count
- ///
- public int SuperMissiles => _data.ReadUInt8(0x7E09CA - 0x7E0750);
-
- ///
- /// Samus's max super missile count
- ///
- public int MaxSuperMissiles => _data.ReadUInt8(0x7E09CC - 0x7E0750);
-
- ///
- /// Samus's current missile count
- ///
- public int Missiles => _data.ReadUInt8(0x7E09C6 - 0x7E0750);
-
- ///
- /// Samus's max missile count
- ///
- public int MaxMissiles => _data.ReadUInt8(0x7E09C8 - 0x7E0750);
-
- ///
- /// Samus's current power bomb count
- ///
- public int PowerBombs => _data.ReadUInt8(0x7E09CE - 0x7E0750);
-
- ///
- /// Samus's max power bomb count
- ///
- public int MaxPowerBombs => _data.ReadUInt8(0x7E09D0 - 0x7E0750);
-
- public bool IsSamusInArea(int minX, int maxX, int minY, int maxY)
- {
- return SamusX >= minX && SamusX <= maxX && SamusY >= minY && SamusY <= 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;
-
- ///
- /// Prints debug data for the state
- ///
- ///
- public override string ToString()
- {
- return $"CurrentRoom: {CurrentRoom} | CurrentRoomInRegion: {CurrentRoomInRegion} | CurrentRegion: {CurrentRegion} | Health: {Energy},{ReserveTanks} | X,Y {SamusX},{SamusY}";
- }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerViewedAction.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerViewedAction.cs
deleted file mode 100644
index 65c1d9b44..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerViewedAction.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// Class for storing an action from viewing something
- /// for a short amount of time
- ///
- public class AutoTrackerViewedAction
- {
- private Action? _action;
-
- ///
- /// Constructor
- ///
- ///
- public AutoTrackerViewedAction(Action action)
- {
- _action = action;
- _ = ExpireAsync();
- }
-
- ///
- /// Expires the action after a period of time
- ///
- private async Task ExpireAsync()
- {
- await Task.Delay(TimeSpan.FromSeconds(15));
- _action = null;
- }
-
- ///
- /// Invokes the action, if it's still valid
- ///
- ///
- public bool Invoke()
- {
- if (_action == null) return false;
- _action.Invoke();
- return true;
- }
-
- ///
- /// If the action is valid
- ///
- public bool IsValid => _action != null;
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerZeldaState.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerZeldaState.cs
deleted file mode 100644
index 99d572d6d..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/AutoTrackerZeldaState.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// Used to retrieve certain states based on the memory in Zelda
- /// See http://alttp.run/hacking/index.php?title=RAM:_Bank_0x7E:_Page_0x00 for details on the memory
- /// These memory address values are the offset from 0x7E0000
- ///
- public class AutoTrackerZeldaState
- {
- private readonly EmulatorMemoryData _data;
-
- ///
- /// Constructor
- ///
- ///
- public AutoTrackerZeldaState(EmulatorMemoryData data)
- {
- _data = data;
- }
-
- ///
- /// The current room the player is in
- ///
- public int CurrentRoom => ReadUInt16(0xA0);
-
- ///
- /// The previous room the player was in
- ///
- public int PreviousRoom => ReadUInt16(0xA2);
-
- ///
- /// The state of the game (Overworld, Dungeon, etc.)
- ///
- public int State => ReadUInt8(0x10);
-
- ///
- /// The secondary state value
- ///
- public int Substate => ReadUInt8(0x11);
-
- ///
- /// The player's Y location
- ///
- public int LinkY => ReadUInt16(0x20);
-
- ///
- /// The player's X Location
- ///
- public int LinkX => ReadUInt16(0x22);
-
- ///
- /// What the player is currently doing
- ///
- public int LinkState => ReadUInt8(0x5D);
-
- ///
- /// Value used to determine if the player is in the light or dark world
- /// Apparently this is used for other calculations as well, so need to be a bit careful
- /// Transitioning from Super Metroid also seems to break this until you go through a portal
- ///
- public int OverworldValue => ReadUInt8(0x7B);
-
- ///
- /// True if Link is on the bottom half of the current room
- ///
- public bool IsOnBottomHalfOfRoom => ReadUInt8(0xAA) == 2;
-
- ///
- /// True if Link is on the right half of the current room
- ///
- public bool IsOnRightHalfOfRoom => ReadUInt8(0xA9) == 1;
-
- ///
- /// The overworld screen that the player is on
- ///
- public int OverworldScreen => ReadUInt16(0x8A);
-
- ///
- /// Reads a specific block of memory
- ///
- /// The address offset from 0x7E0000
- ///
- public int ReadUInt8(int address) => _data.ReadUInt8(address);
-
- ///
- /// Reads a specific block of memory
- ///
- /// The address offset from 0x7E0000
- ///
- public int ReadUInt16(int address) => _data.ReadUInt16(address);
-
- ///
- /// Get debug string
- ///
- ///
- public override string ToString()
- {
- var vertical = IsOnBottomHalfOfRoom ? "Bottom" : "Top";
- var horizontal = IsOnRightHalfOfRoom ? "Right" : "Left";
- return $"Room: {PreviousRoom}->{CurrentRoom} ({vertical}{horizontal}) | State: {State}/{Substate} | X,Y: {LinkX},{LinkY} | LinkState: {LinkState} | OW Screen: {OverworldScreen}";
- }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorAction.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorAction.cs
deleted file mode 100644
index 6f46791ef..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorAction.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Randomizer.Data.WorldData;
-
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// Class used for communicating between the emulator and tracker
- ///
- public class EmulatorAction
- {
- ///
- /// The action to be done by the emulator (read/write/etc)
- ///
- public EmulatorActionType Type { get; set; }
-
- ///
- /// The starting memory address
- ///
- public int Address { get; set; }
-
- ///
- /// The number of bytes to capture from the emulator
- ///
- public int Length { get; set; }
-
- ///
- /// Values for writing
- ///
- public ICollection? WriteValues { get; set; }
-
- ///
- /// The type of memory to read or modify (WRAM, CARTRAM, CARTROM)
- ///
- public MemoryDomain Domain { get; set; }
-
- ///
- /// The game this message is for
- ///
- public Game Game { get; set; } = Game.Both;
-
- ///
- /// Action to perform when getting a response for this from the emulator
- ///
- public Action? Action { get; set; }
-
- ///
- /// The previous data collected for this action
- ///
- public EmulatorMemoryData? PreviousData { get; protected set; }
-
- ///
- /// The latest data collected for this action
- ///
- public EmulatorMemoryData? CurrentData { get; protected set; }
-
- ///
- /// The amount of time that should happen between consecutive reads
- ///
- public double FrequencySeconds = 0;
-
- ///
- /// When this action was last executed
- ///
- public DateTime? LastRunTime;
-
- ///
- /// Update the stored data and invoke the action
- ///
- /// The data collected from the emulator
- public void Invoke(EmulatorMemoryData data)
- {
- PreviousData = CurrentData;
- CurrentData = data;
- LastRunTime = DateTime.Now;
- Action?.Invoke(this);
- }
-
- ///
- /// If this message should be sent based on the game the player is currently in
- ///
- /// The game the player is currently in
- /// If the player has actually started the game
- /// True if the message should be sent.
- public bool ShouldProcess(Game currentGame, bool hasStartedGame)
- {
- return HasExpired && ((!hasStartedGame && Game == Game.Neither) || (hasStartedGame && Game != Game.Neither && (Game == Game.Both || Game == currentGame)));
- }
-
- ///
- /// If the previous action's result has expired
- ///
- public bool HasExpired
- {
- get
- {
- return FrequencySeconds <= 0 || LastRunTime == null ||
- (DateTime.Now - LastRunTime.Value).TotalSeconds >= FrequencySeconds;
- }
- }
-
- ///
- /// Checks if the data has changed between the previous and current collections
- ///
- /// True if the data has changed, false otherwise
- public bool HasDataChanged()
- {
- return CurrentData != null && !CurrentData.Equals(PreviousData);
- }
-
- ///
- /// Cached set of locations for this action
- ///
- public ICollection? Locations { get; set; }
-
- ///
- /// Clears both the previous and current data sets
- ///
- public void ClearData()
- {
- PreviousData = null;
- CurrentData = null;
- }
-
- }
-
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorDataReceivedEventArgs.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorDataReceivedEventArgs.cs
index c53249631..979026de9 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorDataReceivedEventArgs.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorDataReceivedEventArgs.cs
@@ -1,4 +1,7 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking
{
///
/// Event arguments for when connector has received data from the emulator
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorMemoryData.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorMemoryData.cs
deleted file mode 100644
index ead44bd01..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/EmulatorMemoryData.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-using System.Linq;
-
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// Class used to house byte data retrieved from the emulator at a given point in time
- /// Used to retrieve data at locations in memory
- ///
- public class EmulatorMemoryData
- {
- private byte[] _bytes;
-
- ///
- /// Constructor
- ///
- ///
- public EmulatorMemoryData(byte[] bytes)
- {
- _bytes = bytes;
- }
-
- ///
- /// The raw byte array of the data
- ///
- public byte[] Raw
- {
- get
- {
- return _bytes;
- }
- }
-
- ///
- /// Returns the memory value at a location
- ///
- /// The offset location to check
- /// The value from the byte array at that location
- public byte ReadUInt8(int location)
- {
- return _bytes[location];
- }
-
- ///
- /// Gets the memory value for a location and returns if it matches a given flag
- ///
- /// The offset location to check
- /// The flag to check against
- /// True if the flag is set for the memory location.
- public bool CheckBinary8Bit(int location, int flag)
- {
- return (ReadUInt8(location) & flag) == flag;
- }
-
- ///
- /// Checks if a value in memory matches a flag or has been increased to denote obtaining an item
- ///
- /// The previous data to compare to
- /// The offset location to check
- /// The flag to check against
- /// True if the value in memory was set or increased
- public bool CompareUInt8(EmulatorMemoryData previousData, int location, int? flag)
- {
- var prevValue = previousData != null && previousData._bytes.Length > location ? previousData.ReadUInt8(location) : -1;
- var newValue = ReadUInt8(location);
-
- if (newValue > prevValue)
- {
- if (flag != null)
- {
- if ((newValue & flag) == flag)
- {
- return true;
- }
- }
- else if (newValue > 0)
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// Returns the memory value of two bytes / sixteen bits. Note that these are flipped
- /// from what you may expect if you're thinking of it in binary terms. The second byte
- /// is actually multiplied by 0xFF / 256 and added to the first
- ///
- /// The offset location to check
- /// The value from the byte array at that location
- public int ReadUInt16(int location)
- {
- return _bytes[location + 1] * 256 + _bytes[location];
- }
-
- ///
- /// Checks if a binary flag is set for a given set of two bytes / sixteen bits
- ///
- /// The offset location to check
- /// The flag to check against
- /// True if the flag is set for the memory location.
- public bool CheckUInt16(int location, int flag)
- {
- var data = ReadUInt16(location);
- var adjustedFlag = 1 << flag;
- var temp = data & adjustedFlag;
- return temp == adjustedFlag;
- }
-
- ///
- /// Returns if this EmulatorMemoryData equals another
- ///
- ///
- ///
- public override bool Equals(object? other)
- {
- if (other is not EmulatorMemoryData otherData) return false;
- return Enumerable.SequenceEqual(otherData._bytes, _bytes);
- }
-
- ///
- /// Returns the hash code of the bytes array
- ///
- ///
- public override int GetHashCode()
- {
- return _bytes.GetHashCode();
- }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/EmulatorActionType.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/EmulatorActionType.cs
deleted file mode 100644
index 4b6f20a5f..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/EmulatorActionType.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// The type of action
- ///
- public enum EmulatorActionType
- {
- ///
- /// Read a block from memory
- ///
- ReadBlock,
-
- ///
- /// Write data to memory
- ///
- WriteBytes
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/Game.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/Game.cs
deleted file mode 100644
index 49bdf78d1..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/Game.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// Which game(s) the message should be sent to the emulator in
- ///
- public enum Game
- {
- ///
- /// Send if the player has not started the game
- ///
- Neither,
-
- ///
- /// Send if the player is in Super Metroid
- ///
- SM,
-
- ///
- /// Send if the player is in Zelda
- ///
- Zelda,
-
- ///
- /// Send if the player is in either game
- ///
- Both,
-
- ///
- /// Send if the player is viewing the credits
- ///
- Credits
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/MemoryDomain.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/MemoryDomain.cs
deleted file mode 100644
index 98d3b1c7b..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/Enums/MemoryDomain.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking
-{
- ///
- /// The type of memory
- ///
- public enum MemoryDomain
- {
- ///
- /// SNES Memory
- ///
- WRAM,
-
- ///
- /// Cartridge Memory / Save File (AKA SRAM)
- ///
- CartRAM,
-
- ///
- /// Game data saved on cartridge
- ///
- CartROM
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/GameService.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/GameService.cs
index 43db26e99..5a344b048 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/GameService.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/GameService.cs
@@ -3,590 +3,592 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
using Randomizer.Shared;
using Randomizer.SMZ3.Tracking.Services;
using Randomizer.SMZ3.Tracking.VoiceCommands;
using Randomizer.Data.WorldData;
+using Randomizer.Shared.Enums;
using Randomizer.SMZ3.Contracts;
-namespace Randomizer.SMZ3.Tracking.AutoTracking
+namespace Randomizer.SMZ3.Tracking.AutoTracking;
+
+///
+/// Service that handles interacting with the game via
+/// auto tracker
+///
+public class GameService : TrackerModule, IGameService
{
+ private IAutoTracker? AutoTracker => Tracker.AutoTracker;
+ private readonly ILogger _logger;
+ private readonly int _trackerPlayerId;
+ private int _itemCounter;
+ private readonly Dictionary _emulatorActions = new();
+
///
- /// Service that handles interacting with the game via
- /// auto tracker
+ /// Initializes a new instance of the
+ /// class.
///
- public class GameService : TrackerModule
+ /// The tracker instance.
+ /// 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(ITracker tracker, IItemService itemService, IWorldService worldService, ILogger logger, IWorldAccessor worldAccessor)
+ : base(tracker, itemService, worldService, logger)
{
- private AutoTracker? AutoTracker => Tracker.AutoTracker;
- private readonly ILogger _logger;
- private readonly int _trackerPlayerId;
- private int _itemCounter;
- private readonly Dictionary _emulatorActions = new();
-
- ///
- /// Initializes a new instance of the
- /// class.
- ///
- /// The tracker instance.
- /// 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(Tracker tracker, IItemService itemService, IWorldService worldService, ILogger logger, IWorldAccessor worldAccessor)
- : base(tracker, itemService, worldService, logger)
- {
- Tracker.GameService = this;
- _logger = logger;
- _trackerPlayerId = worldAccessor.Worlds.Count > 0 ? worldAccessor.Worlds.Count : 0;
- }
+ Tracker.GameService = this;
+ _logger = logger;
+ _trackerPlayerId = worldAccessor.Worlds.Count > 0 ? worldAccessor.Worlds.Count : 0;
+ }
- ///
- /// Updates memory values so both SM and Z3 will cancel any pending MSU resumes and play
- /// all tracks from the start until new resume points have been stored.
- ///
- /// True, even if it didn't do anything
- public void TryCancelMsuResume()
+ ///
+ /// Updates memory values so both SM and Z3 will cancel any pending MSU resumes and play
+ /// all tracks from the start until new resume points have been stored.
+ ///
+ /// True, even if it didn't do anything
+ public void TryCancelMsuResume()
+ {
+ if (IsInGame(Game.SM))
{
- if (IsInGame(Game.SM))
+ // Zero out SM's NO_RESUME_AFTER_LO and NO_RESUME_AFTER_HI variables
+ AutoTracker?.WriteToMemory(new EmulatorAction()
{
- // Zero out SM's NO_RESUME_AFTER_LO and NO_RESUME_AFTER_HI variables
- AutoTracker?.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E033A, // As declared in sm/msu.asm
- WriteValues = new List() { 0, 0, 0, 0 }
- });
- }
-
- if (IsInGame(Game.Zelda))
- {
- // Zero out Z3's MSUResumeTime variable
- AutoTracker?.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E1E6B, // As declared in z3/randomizer/ram.asm
- WriteValues = new List() { 0, 0, 0, 0 }
- });
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E033A, // As declared in sm/msu.asm
+ WriteValues = new List() { 0, 0, 0, 0 }
+ });
}
- ///
- /// Gives an item to the player
- ///
- /// The item to give
- /// The id of the player giving the item to the player (null for tracker)
- /// False if it is currently unable to give an item to the player
- public bool TryGiveItem(Item item, int? fromPlayerId)
+ if (IsInGame(Game.Zelda))
{
- return TryGiveItems(new List
- () { item }, fromPlayerId ?? _trackerPlayerId);
+ // Zero out Z3's MSUResumeTime variable
+ AutoTracker?.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E1E6B, // As declared in z3/randomizer/ram.asm
+ WriteValues = new List() { 0, 0, 0, 0 }
+ });
}
+ }
- ///
- /// Gives a series of items to the player
- ///
- /// The list of items to give to the player
- /// The id of the player giving the item to the player
- /// False if it is currently unable to give an item to the player
- public bool TryGiveItems(List
- items, int fromPlayerId)
+ ///
+ /// Gives an item to the player
+ ///
+ /// The item to give
+ /// The id of the player giving the item to the player (null for tracker)
+ /// False if it is currently unable to give an item to the player
+ public bool TryGiveItem(Item item, int? fromPlayerId)
+ {
+ return TryGiveItems(new List
- () { item }, fromPlayerId ?? _trackerPlayerId);
+ }
+
+ ///
+ /// Gives a series of items to the player
+ ///
+ /// The list of items to give to the player
+ /// The id of the player giving the item to the player
+ /// False if it is currently unable to give an item to the player
+ public bool TryGiveItems(List
- items, int fromPlayerId)
+ {
+ if (!IsInGame())
{
- if (!IsInGame())
- {
- return false;
- }
+ return false;
+ }
- Tracker.TrackItems(items, true, true);
+ Tracker.TrackItems(items, true, true);
- return TryGiveItemTypes(items.Select(x => (x.Type, fromPlayerId)).ToList());
- }
+ return TryGiveItemTypes(items.Select(x => (x.Type, fromPlayerId)).ToList());
+ }
- ///
- /// Gives a series of item types from particular players
- ///
- /// The list of item types and the players that are giving the item to the player
- /// False if it is currently unable to give the items to the player
- public bool TryGiveItemTypes(List<(ItemType type, int fromPlayerId)> items)
+ ///
+ /// Gives a series of item types from particular players
+ ///
+ /// The list of item types and the players that are giving the item to the player
+ /// False if it is currently unable to give the items to the player
+ public bool TryGiveItemTypes(List<(ItemType type, int fromPlayerId)> items)
+ {
+ if (!IsInGame())
{
- if (!IsInGame())
- {
- return false;
- }
+ return false;
+ }
- var tempItemCounter = _itemCounter;
- EmulatorAction action;
+ var tempItemCounter = _itemCounter;
+ EmulatorAction action;
- // First give the player all of the requested items
- // Batch them into chunks of 50 due to byte limit for QUSB2SNES
- foreach (var batch in items.Chunk(50))
+ // First give the player all of the requested items
+ // Batch them into chunks of 50 due to byte limit for QUSB2SNES
+ foreach (var batch in items.Chunk(50))
+ {
+ var bytes = new List();
+ foreach (var item in batch)
{
- var bytes = new List();
- foreach (var item in batch)
- {
- bytes.AddRange(Int16ToBytes(item.fromPlayerId));
- bytes.AddRange(Int16ToBytes((int)item.type));
- }
-
- action = new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.CartRAM,
- Address = 0xA26000 + (tempItemCounter * 4),
- WriteValues = bytes
- };
- AutoTracker!.WriteToMemory(action);
-
- tempItemCounter += batch.Length;
+ bytes.AddRange(Int16ToBytes(item.fromPlayerId));
+ bytes.AddRange(Int16ToBytes((int)item.type));
}
- // Up the item counter to have them actually pick it up
action = new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.CartRAM,
- Address = 0xA26602,
- WriteValues = Int16ToBytes(tempItemCounter)
+ Address = 0xA26000 + (tempItemCounter * 4),
+ WriteValues = bytes
};
AutoTracker!.WriteToMemory(action);
- _itemCounter = tempItemCounter;
-
- return true;
+ tempItemCounter += batch.Length;
}
- ///
- /// Restores the player to max health
- ///
- /// False if it is currently unable to give an item to the player
- public bool TryHealPlayer()
+ // Up the item counter to have them actually pick it up
+ action = new EmulatorAction()
{
- if (!IsInGame())
- {
- return false;
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.CartRAM,
+ Address = 0xA26602,
+ WriteValues = Int16ToBytes(tempItemCounter)
+ };
+ AutoTracker!.WriteToMemory(action);
- if (AutoTracker!.CurrentGame == Game.Zelda)
- {
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7EF372,
- WriteValues = new List() { 0xA0 }
- });
-
- return true;
- }
- else if (AutoTracker.CurrentGame == Game.SM && AutoTracker.MetroidState != null)
- {
- var maxHealth = AutoTracker.MetroidState.MaxEnergy;
- var maxReserves = AutoTracker.MetroidState.MaxReserveTanks;
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E09C2,
- WriteValues = Int16ToBytes(maxHealth)
- });
-
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E09D6,
- WriteValues = Int16ToBytes(maxReserves)
- });
-
- return true;
- }
+ _itemCounter = tempItemCounter;
+ return true;
+ }
+
+ ///
+ /// Restores the player to max health
+ ///
+ /// False if it is currently unable to give an item to the player
+ public bool TryHealPlayer()
+ {
+ if (!IsInGame())
+ {
return false;
}
- ///
- /// Fully fills the player's magic
- ///
- /// False if it is currently unable to give magic to the player
- public bool TryFillMagic()
+ if (AutoTracker!.CurrentGame == Game.Zelda)
{
- if (!IsInGame(Game.Zelda))
- {
- return false;
- }
-
- AutoTracker!.WriteToMemory(new EmulatorAction()
+ AutoTracker.WriteToMemory(new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.WRAM,
- Address = 0x7EF373,
- WriteValues = new List() { 0x80 }
+ Address = 0x7EF372,
+ WriteValues = new List() { 0xA0 }
});
return true;
}
-
- ///
- /// Fully fills the player's bombs to capacity
- ///
- /// False if it is currently unable to give bombs to the player
- public bool TryFillZeldaBombs()
+ else if (AutoTracker.CurrentGame == Game.SM && AutoTracker.MetroidState != null)
{
- if (!IsInGame(Game.Zelda))
+ var maxHealth = AutoTracker.MetroidState.MaxEnergy;
+ var maxReserves = AutoTracker.MetroidState.MaxReserveTanks;
+ AutoTracker.WriteToMemory(new EmulatorAction()
{
- return false;
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09C2,
+ WriteValues = Int16ToBytes(maxHealth)
+ });
- AutoTracker!.WriteToMemory(new EmulatorAction()
+ AutoTracker.WriteToMemory(new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.WRAM,
- Address = 0x7EF375,
- WriteValues = new List() { 0xFF }
+ Address = 0x7E09D6,
+ WriteValues = Int16ToBytes(maxReserves)
});
return true;
}
- ///
- /// Fully fills the player's arrows
- ///
- /// False if it is currently unable to give arrows to the player
- public bool TryFillArrows()
+ return false;
+ }
+
+ ///
+ /// Fully fills the player's magic
+ ///
+ /// False if it is currently unable to give magic to the player
+ public bool TryFillMagic()
+ {
+ if (!IsInGame(Game.Zelda))
{
- if (!IsInGame(Game.Zelda))
- {
- return false;
- }
+ return false;
+ }
- AutoTracker!.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7EF376,
- WriteValues = new List() { 0x80 }
- });
+ AutoTracker!.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7EF373,
+ WriteValues = new List() { 0x80 }
+ });
- return true;
+ return true;
+ }
+
+ ///
+ /// Fully fills the player's bombs to capacity
+ ///
+ /// False if it is currently unable to give bombs to the player
+ public bool TryFillZeldaBombs()
+ {
+ if (!IsInGame(Game.Zelda))
+ {
+ return false;
}
- ///
- /// Fully fills the player's rupees (sets to 2000)
- ///
- /// False if it is currently unable to give rupees to the player
- public bool TryFillRupees()
+ AutoTracker!.WriteToMemory(new EmulatorAction()
{
- if (!IsInGame(Game.Zelda))
- {
- return false;
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7EF375,
+ WriteValues = new List() { 0xFF }
+ });
- var bytes = Int16ToBytes(2000);
+ return true;
+ }
- // Writing the target value to $7EF360 makes the rupee count start counting toward it.
- // Writing the target value to $7EF362 immediately sets the rupee count, but then it starts counting back toward where it was.
- // Writing the target value to both locations immediately sets the rupee count and keeps it there.
- AutoTracker!.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7EF360,
- WriteValues = bytes.Concat(bytes).ToList()
- });
+ ///
+ /// Fully fills the player's arrows
+ ///
+ /// False if it is currently unable to give arrows to the player
+ public bool TryFillArrows()
+ {
+ if (!IsInGame(Game.Zelda))
+ {
+ return false;
+ }
- return true;
+ AutoTracker!.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7EF376,
+ WriteValues = new List() { 0x80 }
+ });
+
+ return true;
+ }
+
+ ///
+ /// Fully fills the player's rupees (sets to 2000)
+ ///
+ /// False if it is currently unable to give rupees to the player
+ public bool TryFillRupees()
+ {
+ if (!IsInGame(Game.Zelda))
+ {
+ return false;
}
- ///
- /// Fully fills the player's missiles
- ///
- /// False if it is currently unable to give missiles to the player
- public bool TryFillMissiles()
+ var bytes = Int16ToBytes(2000);
+
+ // Writing the target value to $7EF360 makes the rupee count start counting toward it.
+ // Writing the target value to $7EF362 immediately sets the rupee count, but then it starts counting back toward where it was.
+ // Writing the target value to both locations immediately sets the rupee count and keeps it there.
+ AutoTracker!.WriteToMemory(new EmulatorAction()
{
- if (!IsInGame(Game.SM))
- {
- return false;
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7EF360,
+ WriteValues = bytes.Concat(bytes).ToList()
+ });
- var maxMissiles = AutoTracker!.MetroidState?.MaxMissiles ?? 0;
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E09C6,
- WriteValues = Int16ToBytes(maxMissiles)
- });
+ return true;
+ }
- return true;
+ ///
+ /// Fully fills the player's missiles
+ ///
+ /// False if it is currently unable to give missiles to the player
+ public bool TryFillMissiles()
+ {
+ if (!IsInGame(Game.SM))
+ {
+ return false;
}
- ///
- /// Fully fills the player's super missiles
- ///
- /// False if it is currently unable to give super missiles to the player
- public bool TryFillSuperMissiles()
+ var maxMissiles = AutoTracker!.MetroidState?.MaxMissiles ?? 0;
+ AutoTracker.WriteToMemory(new EmulatorAction()
{
- if (!IsInGame(Game.SM))
- {
- return false;
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09C6,
+ WriteValues = Int16ToBytes(maxMissiles)
+ });
- var maxSuperMissiles = AutoTracker!.MetroidState?.MaxSuperMissiles ?? 0;
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E09CA,
- WriteValues = Int16ToBytes(maxSuperMissiles)
- });
+ return true;
+ }
- return true;
+ ///
+ /// Fully fills the player's super missiles
+ ///
+ /// False if it is currently unable to give super missiles to the player
+ public bool TryFillSuperMissiles()
+ {
+ if (!IsInGame(Game.SM))
+ {
+ return false;
}
- ///
- /// Fully fills the player's power bombs
- ///
- /// False if it is currently unable to give power bombs to the player
- public bool TryFillPowerBombs()
+ var maxSuperMissiles = AutoTracker!.MetroidState?.MaxSuperMissiles ?? 0;
+ AutoTracker.WriteToMemory(new EmulatorAction()
{
- if (!IsInGame(Game.SM))
- {
- return false;
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09CA,
+ WriteValues = Int16ToBytes(maxSuperMissiles)
+ });
- var maxPowerBombs = AutoTracker!.MetroidState?.MaxPowerBombs ?? 0;
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E09CE,
- WriteValues = Int16ToBytes(maxPowerBombs)
- });
+ return true;
+ }
- return true;
+ ///
+ /// Fully fills the player's power bombs
+ ///
+ /// False if it is currently unable to give power bombs to the player
+ public bool TryFillPowerBombs()
+ {
+ if (!IsInGame(Game.SM))
+ {
+ return false;
}
- ///
- /// Kills the player by removing their health and dealing damage to them
- ///
- /// True if successful
- public bool TryKillPlayer()
+ var maxPowerBombs = AutoTracker!.MetroidState?.MaxPowerBombs ?? 0;
+ AutoTracker.WriteToMemory(new EmulatorAction()
{
- if (!IsInGame())
- {
- _logger.LogWarning("Could not kill player as they are not in game");
- return false;
- }
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09CE,
+ WriteValues = Int16ToBytes(maxPowerBombs)
+ });
- if (AutoTracker!.CurrentGame == Game.Zelda)
- {
- MarkRecentlyKilled();
-
- // Set health to 0
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7EF36D,
- WriteValues = new List() { 0x0 }
- });
-
- // Deal 1 heart of damage
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E0373,
- WriteValues = new List() { 0x8 }
- });
-
- return true;
- }
- else if (AutoTracker.CurrentGame == Game.SM)
- {
- MarkRecentlyKilled();
-
- // Empty reserves
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E09D6,
- WriteValues = new List() { 0x0, 0x0 }
- });
-
- // Set HP to 1 (to prevent saving with 0 energy)
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E09C2,
- WriteValues = new List() { 0x1, 0x0 }
- });
-
- // Deal 255 damage to player
- AutoTracker.WriteToMemory(new EmulatorAction()
- {
- Type = EmulatorActionType.WriteBytes,
- Domain = MemoryDomain.WRAM,
- Address = 0x7E0A50,
- WriteValues = new List() { 0xFF }
- });
-
- return true;
- }
+ return true;
+ }
- _logger.LogWarning("Could not kill player as they are not in either Zelda or Metroid currently");
+ ///
+ /// Kills the player by removing their health and dealing damage to them
+ ///
+ /// True if successful
+ public bool TryKillPlayer()
+ {
+ if (!IsInGame())
+ {
+ _logger.LogWarning("Could not kill player as they are not in game");
return false;
}
- ///
- /// Sets the player to have the requirements for a crystal flash
- ///
- /// True if successful
- public bool TrySetupCrystalFlash()
+ if (AutoTracker!.CurrentGame == Game.Zelda)
{
- if (!IsInGame(Game.SM))
- {
- return false;
- }
+ MarkRecentlyKilled();
- // Set HP to 50 health
- AutoTracker!.WriteToMemory(new EmulatorAction()
+ // Set health to 0
+ AutoTracker.WriteToMemory(new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.WRAM,
- Address = 0x7E09C2,
- WriteValues = new List() { 0x32, 0x0 }
+ Address = 0x7EF36D,
+ WriteValues = new List() { 0x0 }
});
- // Empty reserves
+ // Deal 1 heart of damage
AutoTracker.WriteToMemory(new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.WRAM,
- Address = 0x7E09D6,
- WriteValues = new List() { 0x0, 0x0 }
+ Address = 0x7E0373,
+ WriteValues = new List() { 0x8 }
});
- // Fill missiles
- var maxMissiles = AutoTracker.MetroidState?.MaxMissiles ?? 0;
+ return true;
+ }
+ else if (AutoTracker.CurrentGame == Game.SM)
+ {
+ MarkRecentlyKilled();
+
+ // Empty reserves
AutoTracker.WriteToMemory(new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.WRAM,
- Address = 0x7E09C6,
- WriteValues = Int16ToBytes(maxMissiles)
+ Address = 0x7E09D6,
+ WriteValues = new List() { 0x0, 0x0 }
});
- // Fill super missiles
- var maxSuperMissiles = AutoTracker.MetroidState?.MaxSuperMissiles ?? 0;
+ // Set HP to 1 (to prevent saving with 0 energy)
AutoTracker.WriteToMemory(new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.WRAM,
- Address = 0x7E09CA,
- WriteValues = Int16ToBytes(maxSuperMissiles)
+ Address = 0x7E09C2,
+ WriteValues = new List() { 0x1, 0x0 }
});
- // Fill power bombs
- var maxPowerBombs = AutoTracker.MetroidState?.MaxPowerBombs ?? 0;
+ // Deal 255 damage to player
AutoTracker.WriteToMemory(new EmulatorAction()
{
Type = EmulatorActionType.WriteBytes,
Domain = MemoryDomain.WRAM,
- Address = 0x7E09CE,
- WriteValues = Int16ToBytes(maxPowerBombs)
+ Address = 0x7E0A50,
+ WriteValues = new List() { 0xFF }
});
return true;
}
- ///
- /// Gives the player any items that tracker thinks they should have but are not in memory as having been gifted
- ///
- ///
- public void SyncItems(EmulatorAction action)
+ _logger.LogWarning("Could not kill player as they are not in either Zelda or Metroid currently");
+ return false;
+ }
+
+ ///
+ /// Sets the player to have the requirements for a crystal flash
+ ///
+ /// True if successful
+ public bool TrySetupCrystalFlash()
+ {
+ if (!IsInGame(Game.SM))
{
- if (AutoTracker?.HasValidState != true)
- {
- return;
- }
+ return false;
+ }
- _emulatorActions[action.Address] = action;
+ // Set HP to 50 health
+ AutoTracker!.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09C2,
+ WriteValues = new List() { 0x32, 0x0 }
+ });
+
+ // Empty reserves
+ AutoTracker.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09D6,
+ WriteValues = new List() { 0x0, 0x0 }
+ });
+
+ // Fill missiles
+ var maxMissiles = AutoTracker.MetroidState?.MaxMissiles ?? 0;
+ AutoTracker.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09C6,
+ WriteValues = Int16ToBytes(maxMissiles)
+ });
+
+ // Fill super missiles
+ var maxSuperMissiles = AutoTracker.MetroidState?.MaxSuperMissiles ?? 0;
+ AutoTracker.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09CA,
+ WriteValues = Int16ToBytes(maxSuperMissiles)
+ });
+
+ // Fill power bombs
+ var maxPowerBombs = AutoTracker.MetroidState?.MaxPowerBombs ?? 0;
+ AutoTracker.WriteToMemory(new EmulatorAction()
+ {
+ Type = EmulatorActionType.WriteBytes,
+ Domain = MemoryDomain.WRAM,
+ Address = 0x7E09CE,
+ WriteValues = Int16ToBytes(maxPowerBombs)
+ });
- if (!_emulatorActions.ContainsKey(0xA26000) || !_emulatorActions.ContainsKey(0xA26300) || !_emulatorActions.Values.All(x =>
- x.LastRunTime != null && (DateTime.Now - x.LastRunTime.Value).TotalSeconds < 5))
- {
- return;
- }
+ return true;
+ }
- var data = _emulatorActions[0xA26000].CurrentData!.Raw.Concat(_emulatorActions[0xA26300].CurrentData!.Raw).ToArray();
+ ///
+ /// Gives the player any items that tracker thinks they should have but are not in memory as having been gifted
+ ///
+ ///
+ public void SyncItems(EmulatorAction action)
+ {
+ if (AutoTracker?.HasValidState != true)
+ {
+ return;
+ }
- var previouslyGiftedItems = new List<(ItemType type, int fromPlayerId)>();
- for (var i = 0; i < 0x150; i++)
- {
- var item = (ItemType)BitConverter.ToUInt16(data.AsSpan(i * 4 + 2, 2));
- if (item == ItemType.Nothing)
- {
- continue;
- }
-
- var playerId = BitConverter.ToUInt16(data.AsSpan(i * 4, 2));
- previouslyGiftedItems.Add((item, playerId));
- }
+ _emulatorActions[action.Address] = action;
- _itemCounter = previouslyGiftedItems.Count;
+ if (!_emulatorActions.ContainsKey(0xA26000) || !_emulatorActions.ContainsKey(0xA26300) || !_emulatorActions.Values.All(x =>
+ x.LastRunTime != null && (DateTime.Now - x.LastRunTime.Value).TotalSeconds < 5))
+ {
+ return;
+ }
- var otherCollectedItems = WorldService.Worlds.SelectMany(x => x.Locations)
- .Where(x => x.State.ItemWorldId == Tracker.World.Id && x.State.WorldId != Tracker.World.Id &&
- x.State.Autotracked).Select(x => (x.State.Item, x.State.WorldId)).ToList();
+ var data = _emulatorActions[0xA26000].CurrentData!.Raw.Concat(_emulatorActions[0xA26300].CurrentData!.Raw).ToArray();
- foreach (var item in previouslyGiftedItems)
+ var previouslyGiftedItems = new List<(ItemType type, int fromPlayerId)>();
+ for (var i = 0; i < 0x150; i++)
+ {
+ var item = (ItemType)BitConverter.ToUInt16(data.AsSpan(i * 4 + 2, 2));
+ if (item == ItemType.Nothing)
{
- otherCollectedItems.Remove(item);
+ continue;
}
- if (otherCollectedItems.Any())
- {
- _logger.LogInformation("Giving player {ItemCount} missing items", otherCollectedItems.Count);
- TryGiveItemTypes(otherCollectedItems);
- }
+ var playerId = BitConverter.ToUInt16(data.AsSpan(i * 4, 2));
+ previouslyGiftedItems.Add((item, playerId));
}
- ///
- /// If the player was recently killed by the game service
- ///
- public bool PlayerRecentlyKilled { get; private set; }
+ _itemCounter = previouslyGiftedItems.Count;
- private async void MarkRecentlyKilled()
+ var otherCollectedItems = WorldService.Worlds.SelectMany(x => x.Locations)
+ .Where(x => x.State.ItemWorldId == Tracker.World.Id && x.State.WorldId != Tracker.World.Id &&
+ x.State.Autotracked).Select(x => (x.State.Item, x.State.WorldId)).ToList();
+
+ foreach (var item in previouslyGiftedItems)
{
- PlayerRecentlyKilled = true;
- await Task.Delay(TimeSpan.FromSeconds(10));
- PlayerRecentlyKilled = false;
+ otherCollectedItems.Remove(item);
}
- private static byte[] Int16ToBytes(int value)
+ if (otherCollectedItems.Any())
{
- var bytes = BitConverter.GetBytes((short)value).ToList();
- if (!BitConverter.IsLittleEndian)
- {
- bytes.Reverse();
- }
- return bytes.ToArray();
+ _logger.LogInformation("Giving player {ItemCount} missing items", otherCollectedItems.Count);
+ TryGiveItemTypes(otherCollectedItems);
}
+ }
+
+ ///
+ /// If the player was recently killed by the game service
+ ///
+ public bool PlayerRecentlyKilled { get; private set; }
- private bool IsInGame(Game game = Game.Both)
+ private async void MarkRecentlyKilled()
+ {
+ PlayerRecentlyKilled = true;
+ await Task.Delay(TimeSpan.FromSeconds(10));
+ PlayerRecentlyKilled = false;
+ }
+
+ private static byte[] Int16ToBytes(int value)
+ {
+ var bytes = BitConverter.GetBytes((short)value).ToList();
+ if (!BitConverter.IsLittleEndian)
{
- if (AutoTracker is { IsConnected: true, IsInSMZ3: true, HasValidState: true })
- {
- return game == Game.Both || AutoTracker.CurrentGame == game;
- }
- return false;
+ bytes.Reverse();
}
+ return bytes.ToArray();
+ }
- public override void AddCommands()
+ private bool IsInGame(Game game = Game.Both)
+ {
+ if (AutoTracker is { IsConnected: true, IsInSMZ3: true, HasValidState: true })
{
-
+ return game == Game.Both || AutoTracker.CurrentGame == game;
}
+ return false;
+ }
+
+ public override void AddCommands()
+ {
+
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/IEmulatorConnector.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/IEmulatorConnector.cs
index f6b42a678..139970730 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/IEmulatorConnector.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/IEmulatorConnector.cs
@@ -1,4 +1,5 @@
using System;
+using Randomizer.Data.Tracking;
namespace Randomizer.SMZ3.Tracking.AutoTracking
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/LuaConnector.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/LuaConnector.cs
index d511d6474..6b08cc152 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/LuaConnector.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/LuaConnector.cs
@@ -6,6 +6,9 @@
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+using Randomizer.Shared.Enums;
namespace Randomizer.SMZ3.Tracking.AutoTracking
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs
index 017528d74..98ff21a6e 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs
@@ -1,4 +1,7 @@
using System.Linq;
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
using Randomizer.Data.WorldData.Regions;
namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
@@ -18,7 +21,7 @@ public class ChangedMetroidRegion : IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState.CurrentRegion != _previousMetroidRegionValue || tracker.CurrentRegion?.GetRegion(tracker.World) is Z3Region)
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs
index 63bd09715..248b2d50c 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs
@@ -1,4 +1,8 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
{
///
/// Metroid state check for nearing Crocomire
@@ -13,7 +17,7 @@ public class Crocomire : IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState.CurrentRegion == 2 && currentState.CurrentRoomInRegion == 9 && currentState.SamusX >= 3000 && currentState.SamusY > 500)
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/CrumbleShaft.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/CrumbleShaft.cs
index f34ca993b..d4a389ae7 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/CrumbleShaft.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/CrumbleShaft.cs
@@ -1,4 +1,8 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
{
///
/// Metroid state check for reaching Crumble Shaft
@@ -13,7 +17,7 @@ public class CrumbleShaft : IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState.CurrentRegion == 2 && currentState.CurrentRoomInRegion == 8 && prevState.CurrentRoomInRegion == 4)
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/IMetroidStateCheck.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/IMetroidStateCheck.cs
index e9e56b7bb..989d48bd3 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/IMetroidStateCheck.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/IMetroidStateCheck.cs
@@ -1,4 +1,8 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
{
///
/// Abstract class for various Metroid state checks
@@ -12,6 +16,6 @@ public interface IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState);
+ bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState);
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/KraidsAwfulSon.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/KraidsAwfulSon.cs
index bd24c9042..78d81b23b 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/KraidsAwfulSon.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/KraidsAwfulSon.cs
@@ -1,4 +1,8 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
{
///
/// Metroid state check for nearing Kraid's awful son
@@ -13,7 +17,7 @@ public class KraidsAwfulSon : IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState.CurrentRegion == 1 && currentState.CurrentRoomInRegion == 45 && prevState.CurrentRoomInRegion == 44)
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs
index 12a285586..c67e699cb 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs
@@ -1,4 +1,7 @@
using System.Linq;
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
using Randomizer.Data.WorldData.Regions;
using Randomizer.SMZ3.Tracking.Services;
@@ -31,7 +34,7 @@ public MetroidDeath(IItemService itemService)
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState.Energy != 0 || currentState.ReserveTanks != 0 || prevState.Energy == 0 ||
currentState.CurrentRoom == 0 && currentState is { CurrentRegion: 0, SamusY: 0 })
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs
index 522849fdc..4e65f9027 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs
@@ -1,5 +1,6 @@
-using Randomizer.Shared;
-using Randomizer.SMZ3.Tracking.Services;
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
+using Randomizer.Shared;
namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
{
@@ -23,7 +24,7 @@ public Mockball(IItemService itemService)
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (_itemService.IsTracked(ItemType.SpeedBooster))
return false;
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Ridley.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Ridley.cs
index 8f150ec3e..9fddf811b 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Ridley.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Ridley.cs
@@ -1,4 +1,8 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
{
///
/// Metroid state check related to greeting the Ridley face
@@ -13,7 +17,7 @@ public class Ridley : IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState.CurrentRegion == 2 && currentState.CurrentRoomInRegion == 37 && currentState.SamusX <= 375 && currentState.SamusX >= 100 && currentState.SamusY <= 200)
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs
index a2311fe58..b75b10843 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs
@@ -1,4 +1,7 @@
using System.Linq;
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
using Randomizer.Shared;
namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
@@ -16,7 +19,7 @@ public class Shaktool : IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState is { CurrentRegion: 4, CurrentRoomInRegion: 36 } && prevState.CurrentRoomInRegion == 28 &&
tracker.World.FindLocation(LocationId.InnerMaridiaSpringBall)?.State.Cleared != true &&
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/SporeSpawn.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/SporeSpawn.cs
index a49906407..a56c425a9 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/SporeSpawn.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/MetroidStateChecks/SporeSpawn.cs
@@ -1,4 +1,8 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks
{
///
/// Metroid state check related to Spore Spawn
@@ -13,7 +17,7 @@ public class SporeSpawn : IMetroidStateCheck
/// The current state in Super Metroid
/// The previous state in Super Metroid
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState)
{
if (currentState.CurrentRegion == 1 && currentState.CurrentRoomInRegion == 22 && prevState.CurrentRoomInRegion == 9)
{
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/USB2SNESConnector.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/USB2SNESConnector.cs
index 7b859dd97..97df8282a 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/USB2SNESConnector.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/USB2SNESConnector.cs
@@ -6,6 +6,9 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+using Randomizer.Shared.Enums;
using Websocket.Client;
namespace Randomizer.SMZ3.Tracking.AutoTracking
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs
index 494525c8c..9df5967fb 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs
@@ -1,34 +1,35 @@
using System.Linq;
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
using Randomizer.Data.WorldData.Regions;
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
+
+///
+/// Zelda State check for changing overworld locations
+/// Checks if the game is in the overworld state and was either not in the overworld state previously or has changed overworld screens
+///
+public class ChangedOverworld : IZeldaStateCheck
{
///
- /// Zelda State check for changing overworld locations
- /// Checks if the game is in the overworld state and was either not in the overworld state previously or has changed overworld screens
+ /// Executes the check for the current state
///
- public class ChangedOverworld : IZeldaStateCheck
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ if (currentState.State == 0x09 && (prevState.State != 0x09 || currentState.OverworldScreen != prevState.OverworldScreen))
{
- if (currentState.State == 0x09 && (prevState.State != 0x09 || currentState.OverworldScreen != prevState.OverworldScreen))
- {
- var region = tracker.World.Regions.Where(x => x is Z3Region)
- .Select(x => x as Z3Region)
- .FirstOrDefault(x => x != null && x.StartingRooms != null && x.StartingRooms.Contains(currentState.OverworldScreen) && x.IsOverworld);
- if (region == null) return false;
+ var region = tracker.World.Regions.Where(x => x is Z3Region)
+ .Select(x => x as Z3Region)
+ .FirstOrDefault(x => x != null && x.StartingRooms != null && x.StartingRooms.Contains(currentState.OverworldScreen) && x.IsOverworld);
+ if (region == null) return false;
- tracker.UpdateRegion(region, tracker.Options.AutoTrackerChangeMap);
- return true;
- }
- return false;
+ tracker.UpdateRegion(region, tracker.Options.AutoTrackerChangeMap);
+ return true;
}
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/DiverDown.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/DiverDown.cs
index 9afcb1a36..4638171cb 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/DiverDown.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/DiverDown.cs
@@ -1,33 +1,35 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
+
+///
+/// Zelda State check for performing the diver down trick
+/// Player is walking the lower water area from an unexpected direction
+///
+public class DiverDown : IZeldaStateCheck
{
///
- /// Zelda State check for performing the diver down trick
- /// Player is walking the lower water area from an unexpected direction
+ /// Executes the check for the current state
///
- public class DiverDown : IZeldaStateCheck
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ // Back diver down
+ if (currentState.CurrentRoom == 118 && currentState.LinkX < 3474 && (currentState.LinkX < 3424 || currentState.LinkX > 3440) && currentState.LinkY <= 4000 && prevState.LinkY > 4000 && (currentState.LinkState is 0 or 6 or 3) && currentState.IsOnBottomHalfOfRoom && currentState.IsOnRightHalfOfRoom)
{
- // Back diver down
- if (currentState.CurrentRoom == 118 && currentState.LinkX < 3474 && (currentState.LinkX < 3424 || currentState.LinkX > 3440) && currentState.LinkY <= 4000 && prevState.LinkY > 4000 && (currentState.LinkState is 0 or 6 or 3) && currentState.IsOnBottomHalfOfRoom && currentState.IsOnRightHalfOfRoom)
- {
- tracker.SayOnce(x => x.AutoTracker.DiverDown);
- return true;
- }
- // Left side diver down
- else if (currentState.CurrentRoom == 53 && currentState.PreviousRoom == 54 && currentState.LinkX > 2808 && currentState.LinkX < 2850 && currentState.LinkY <= 1940 && prevState.LinkY > 1940 && currentState.LinkX <= prevState.LinkX && (currentState.LinkState is 0 or 6 or 3))
- {
- tracker.SayOnce(x => x.AutoTracker.DiverDown);
- return true;
- }
- return false;
+ tracker.SayOnce(x => x.AutoTracker.DiverDown);
+ return true;
}
+ // Left side diver down
+ else if (currentState.CurrentRoom == 53 && currentState.PreviousRoom == 54 && currentState.LinkX > 2808 && currentState.LinkX < 2850 && currentState.LinkY <= 1940 && prevState.LinkY > 1940 && currentState.LinkX <= prevState.LinkX && (currentState.LinkState is 0 or 6 or 3))
+ {
+ tracker.SayOnce(x => x.AutoTracker.DiverDown);
+ return true;
+ }
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs
index d0f267fec..8e43d7fc8 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs
@@ -1,75 +1,76 @@
using System.Collections.Generic;
using System.Linq;
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
using Randomizer.Data.WorldData.Regions;
using Randomizer.Data.WorldData.Regions.Zelda;
using Randomizer.SMZ3.Contracts;
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
+
+///
+/// Zelda State check for detecting entering a dungeon
+/// Player is now in the dungeon state from the overworld in one of the designated starting rooms
+///
+public class EnteredDungeon : IZeldaStateCheck
{
+ private readonly HashSet _enteredDungeons = new();
+ private readonly IWorldAccessor _worldAccessor;
+
///
- /// Zelda State check for detecting entering a dungeon
- /// Player is now in the dungeon state from the overworld in one of the designated starting rooms
+ /// Constructor
///
- public class EnteredDungeon : IZeldaStateCheck
+ ///
+ public EnteredDungeon(IWorldAccessor worldAccessor)
{
- private readonly HashSet _enteredDungeons = new();
- private readonly IWorldAccessor _worldAccessor;
+ _worldAccessor = worldAccessor;
+ }
- ///
- /// Constructor
- ///
- ///
- public EnteredDungeon(IWorldAccessor worldAccessor)
+ ///
+ /// Executes the check for the current state
+ ///
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ {
+ if (currentState.State == 0x07 && (prevState.State == 0x06 || prevState.State == 0x09 || prevState.State == 0x0F || prevState.State == 0x10 || prevState.State == 0x11))
{
- _worldAccessor = worldAccessor;
- }
+ // Get the region for the room
+ var region = tracker.World.Regions
+ .OfType()
+ .FirstOrDefault(x => x.StartingRooms.Count == 0 && x.StartingRooms.Contains(currentState.CurrentRoom) && !x.IsOverworld);
+ if (region == null) return false;
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
- {
- if (currentState.State == 0x07 && (prevState.State == 0x06 || prevState.State == 0x09 || prevState.State == 0x0F || prevState.State == 0x10 || prevState.State == 0x11))
- {
- // Get the region for the room
- var region = tracker.World.Regions
- .OfType()
- .FirstOrDefault(x => x.StartingRooms.Count == 0 && x.StartingRooms.Contains(currentState.CurrentRoom) && !x.IsOverworld);
- if (region == null) return false;
+ // Get the dungeon info for the room
+ var dungeon = region as IDungeon;
+ if (dungeon == null) return false;
- // Get the dungeon info for the room
- var dungeon = region as IDungeon;
- if (dungeon == null) return false;
+ if (!_worldAccessor.World.Config.ZeldaKeysanity && !_enteredDungeons.Contains(region) && dungeon.IsPendantDungeon)
+ {
+ tracker.Say(tracker.Responses.AutoTracker.EnterPendantDungeon, dungeon.DungeonMetadata.Name, dungeon.DungeonReward?.Metadata.Name);
+ }
+ else if (!_worldAccessor.World.Config.ZeldaKeysanity && region is CastleTower)
+ {
+ tracker.Say(x => x.AutoTracker.EnterHyruleCastleTower);
+ }
+ else if (region is GanonsTower)
+ {
+ var clearedCrystalDungeonCount = tracker.World.Dungeons
+ .Count(x => x.DungeonState.Cleared && x.IsCrystalDungeon);
- if (!_worldAccessor.World.Config.ZeldaKeysanity && !_enteredDungeons.Contains(region) && dungeon.IsPendantDungeon)
- {
- tracker.Say(tracker.Responses.AutoTracker.EnterPendantDungeon, dungeon.DungeonMetadata.Name, dungeon.DungeonReward?.Metadata.Name);
- }
- else if (!_worldAccessor.World.Config.ZeldaKeysanity && region is CastleTower)
+ if (clearedCrystalDungeonCount < 7)
{
- tracker.Say(x => x.AutoTracker.EnterHyruleCastleTower);
+ tracker.SayOnce(x => x.AutoTracker.EnteredGTEarly, clearedCrystalDungeonCount);
}
- else if (region is GanonsTower)
- {
- var clearedCrystalDungeonCount = tracker.World.Dungeons
- .Count(x => x.DungeonState.Cleared && x.IsCrystalDungeon);
-
- if (clearedCrystalDungeonCount < 7)
- {
- tracker.SayOnce(x => x.AutoTracker.EnteredGTEarly, clearedCrystalDungeonCount);
- }
- }
-
- tracker.UpdateRegion(region, tracker.Options.AutoTrackerChangeMap);
- _enteredDungeons.Add(region);
- return true;
}
- return false;
+ tracker.UpdateRegion(region, tracker.Options.AutoTrackerChangeMap);
+ _enteredDungeons.Add(region);
+ return true;
}
+
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FakeFlippers.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FakeFlippers.cs
deleted file mode 100644
index 62d7673cc..000000000
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FakeFlippers.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Linq;
-using Randomizer.Shared;
-
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
-{
- ///
- /// Zelda State check for performing fake flippers
- /// Checks if the player is in the swimming state for two states without the flippers
- ///
- public class FakeFlippers : IZeldaStateCheck
- {
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
- {
- return false;
- /*if (currentState.LinkState == 0x04 && prevState.LinkState == 0x04 && tracker.Items.Any(x => x.InternalItemType == ItemType.Flippers && x.TrackingState == 0))
- {
- tracker.SayOnce(x => x.AutoTracker.FakeFlippers);
- return true;
- }
- return false;*/
- }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromGanon.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromGanon.cs
index fe10ca36f..f3f25a167 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromGanon.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromGanon.cs
@@ -1,26 +1,28 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
+
+///
+/// Zelda State check for falling from Ganon's room
+/// Checks if the current room is the one below Ganon and the previous room was the Ganon room
+///
+public class FallFromGanon : IZeldaStateCheck
{
///
- /// Zelda State check for falling from Ganon's room
- /// Checks if the current room is the one below Ganon and the previous room was the Ganon room
+ /// Executes the check for the current state
///
- public class FallFromGanon : IZeldaStateCheck
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ if (currentState.CurrentRoom == 16 && currentState.PreviousRoom == 0 && prevState.CurrentRoom == 0)
{
- if (currentState.CurrentRoom == 16 && currentState.PreviousRoom == 0 && prevState.CurrentRoom == 0)
- {
- tracker.SayOnce(x => x.AutoTracker.FallFromGanon);
- return true;
- }
- return false;
+ tracker.SayOnce(x => x.AutoTracker.FallFromGanon);
+ return true;
}
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromMoldorm.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromMoldorm.cs
index 4cfa259fe..aed73c3ea 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromMoldorm.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/FallFromMoldorm.cs
@@ -1,33 +1,35 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
+
+///
+/// Zelda State check for falling from the moldorm room(s)
+/// Checks if the current room is the one below moldorm and the previous room was the moldorm room
+///
+public class FallFromMoldorm : IZeldaStateCheck
{
///
- /// Zelda State check for falling from the moldorm room(s)
- /// Checks if the current room is the one below moldorm and the previous room was the moldorm room
+ /// Executes the check for the current state
///
- public class FallFromMoldorm : IZeldaStateCheck
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ // Tower of Hera
+ if (currentState.CurrentRoom == 23 && currentState.PreviousRoom == 7 && prevState.CurrentRoom == 7)
{
- // Tower of Hera
- if (currentState.CurrentRoom == 23 && currentState.PreviousRoom == 7 && prevState.CurrentRoom == 7)
- {
- tracker.SayOnce(x => x.AutoTracker.FallFromMoldorm);
- return true;
- }
- // Ganon's Tower
- else if (currentState.CurrentRoom == 166 && currentState.PreviousRoom == 77 && prevState.CurrentRoom == 77)
- {
- tracker.SayOnce(x => x.AutoTracker.FallFromGTMoldorm);
- return true;
- }
- return false;
+ tracker.SayOnce(x => x.AutoTracker.FallFromMoldorm);
+ return true;
}
+ // Ganon's Tower
+ else if (currentState.CurrentRoom == 166 && currentState.PreviousRoom == 77 && prevState.CurrentRoom == 77)
+ {
+ tracker.SayOnce(x => x.AutoTracker.FallFromGTMoldorm);
+ return true;
+ }
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/HeraPot.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/HeraPot.cs
index ed1ba0f20..631bb394d 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/HeraPot.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/HeraPot.cs
@@ -1,26 +1,28 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
+
+///
+/// Zelda State check for breaking into the Tower of Hera pot
+/// player is in the pot room and did not get there from falling from the two rooms above it
+///
+public class HeraPot : IZeldaStateCheck
{
///
- /// Zelda State check for breaking into the Tower of Hera pot
- /// player is in the pot room and did not get there from falling from the two rooms above it
+ /// Executes the check for the current state
///
- public class HeraPot : IZeldaStateCheck
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ if (currentState.CurrentRoom == 167 && prevState.CurrentRoom == 119 && prevState.PreviousRoom != 49)
{
- if (currentState.CurrentRoom == 167 && prevState.CurrentRoom == 119 && prevState.PreviousRoom != 49)
- {
- tracker.SayOnce(x => x.AutoTracker.HeraPot);
- return true;
- }
- return false;
+ tracker.SayOnce(x => x.AutoTracker.HeraPot);
+ return true;
}
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IZeldaStateCheck.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IZeldaStateCheck.cs
index 2317342f4..d544c47de 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IZeldaStateCheck.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IZeldaStateCheck.cs
@@ -3,6 +3,9 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
{
@@ -18,6 +21,6 @@ public interface IZeldaStateCheck
/// The current state in Zelda
/// The previous state in Zelda
/// True if the state was found
- bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState);
+ bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState);
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IceBreaker.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IceBreaker.cs
index e3ebf23b5..ea42876a8 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IceBreaker.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/IceBreaker.cs
@@ -1,26 +1,28 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
+
+///
+/// Zelda State check for performing the ice breaker trick
+/// player is on the right side of the wall but was previous in the room to the left
+///
+public class IceBreaker : IZeldaStateCheck
{
///
- /// Zelda State check for performing the ice breaker trick
- /// player is on the right side of the wall but was previous in the room to the left
+ /// Executes the check for the current state
///
- public class IceBreaker : IZeldaStateCheck
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ if (currentState.CurrentRoom == 31 && currentState.PreviousRoom == 30 && currentState.LinkX >= 8000 && prevState.LinkX < 8000 && currentState.IsOnRightHalfOfRoom && prevState.IsOnRightHalfOfRoom)
{
- if (currentState.CurrentRoom == 31 && currentState.PreviousRoom == 30 && currentState.LinkX >= 8000 && prevState.LinkX < 8000 && currentState.IsOnRightHalfOfRoom && prevState.IsOnRightHalfOfRoom)
- {
- tracker.SayOnce(x => x.AutoTracker.IceBreaker);
- return true;
- }
- return false;
+ tracker.SayOnce(x => x.AutoTracker.IceBreaker);
+ return true;
}
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/SpeckyClip.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/SpeckyClip.cs
index e25d9a599..246e1e426 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/SpeckyClip.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/SpeckyClip.cs
@@ -1,4 +1,8 @@
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+using Randomizer.Abstractions;
+using Randomizer.Data;
+using Randomizer.Data.Tracking;
+
+namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
{
///
/// Zelda State check for performing the specky clip trick
@@ -13,7 +17,7 @@ public class SpeckyClip : IZeldaStateCheck
/// The current state in Zelda
/// The previous state in Zelda
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
var inCorrectLocation = currentState is
{ CurrentRoom: 55, IsOnBottomHalfOfRoom: true, IsOnRightHalfOfRoom: false };
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs
index 3d9044b95..b0e362e34 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
-
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
using Randomizer.Shared;
using Randomizer.SMZ3.Contracts;
using Randomizer.Data.WorldData.Regions;
@@ -8,168 +9,166 @@
using Randomizer.Data.WorldData.Regions.Zelda.DarkWorld.DeathMountain;
using Randomizer.Data.WorldData.Regions.Zelda.LightWorld;
using Randomizer.Data.WorldData.Regions.Zelda.LightWorld.DeathMountain;
-using Randomizer.SMZ3.Tracking.Services;
using Randomizer.Data.WorldData;
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+namespace Randomizer.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
{
- ///
- /// 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
+ private ITracker? _tracker;
+ private readonly IWorldAccessor _worldAccessor;
+ private bool _lightWorldUpdated;
+ private bool _darkWorldUpdated;
+
+ public ViewedMap(IWorldAccessor worldAccessor, IItemService itemService)
{
- private Tracker? _tracker;
- private readonly IWorldAccessor _worldAccessor;
- private bool _lightWorldUpdated;
- private bool _darkWorldUpdated;
+ _worldAccessor = worldAccessor;
+ Items = itemService;
+ }
- public ViewedMap(IWorldAccessor worldAccessor, IItemService itemService)
- {
- _worldAccessor = worldAccessor;
- Items = itemService;
- }
+ protected World World => _worldAccessor.World;
- protected World World => _worldAccessor.World;
+ protected IItemService Items { get; }
- protected IItemService Items { get; }
+ ///
+ /// Executes the check for the current state
+ ///
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ {
+ if (tracker.AutoTracker == null || (_lightWorldUpdated && _darkWorldUpdated)) return false;
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ if (currentState.State == 14 && currentState.Substate == 7 && currentState.ReadUInt8(0xE0) == 0x80)
{
- if (tracker.AutoTracker == null || (_lightWorldUpdated && _darkWorldUpdated)) return false;
-
- if (currentState.State == 14 && currentState.Substate == 7 && currentState.ReadUInt8(0xE0) == 0x80)
+ _tracker = tracker;
+ var currentRegion = tracker.World.Regions
+ .OfType()
+ .FirstOrDefault(x => x.StartingRooms != null && x.StartingRooms.Contains(currentState.OverworldScreen) && x.IsOverworld);
+ if (currentRegion is LightWorldNorthWest or LightWorldNorthEast or LightWorldSouth or LightWorldDeathMountainEast or LightWorldDeathMountainWest && !_lightWorldUpdated)
{
- _tracker = tracker;
- var currentRegion = tracker.World.Regions
- .OfType()
- .FirstOrDefault(x => x.StartingRooms != null && x.StartingRooms.Contains(currentState.OverworldScreen) && x.IsOverworld);
- if (currentRegion is LightWorldNorthWest or LightWorldNorthEast or LightWorldSouth or LightWorldDeathMountainEast or LightWorldDeathMountainWest && !_lightWorldUpdated)
+ tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(UpdateLightWorldRewards);
+ if (tracker.Options.AutoSaveLookAtEvents)
{
- tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(UpdateLightWorldRewards);
- if (tracker.Options.AutoSaveLookAtEvents)
- {
- tracker.AutoTracker.LatestViewAction.Invoke();
- }
+ tracker.AutoTracker.LatestViewAction.Invoke();
}
- else if (currentRegion is DarkWorldNorthWest or DarkWorldNorthEast or DarkWorldSouth or DarkWorldMire or DarkWorldDeathMountainEast or DarkWorldDeathMountainWest && !_darkWorldUpdated)
+ }
+ else if (currentRegion is DarkWorldNorthWest or DarkWorldNorthEast or DarkWorldSouth or DarkWorldMire or DarkWorldDeathMountainEast or DarkWorldDeathMountainWest && !_darkWorldUpdated)
+ {
+ tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(UpdateDarkWorldRewards);
+ if (tracker.Options.AutoSaveLookAtEvents)
{
- tracker.AutoTracker.LatestViewAction = new AutoTrackerViewedAction(UpdateDarkWorldRewards);
- if (tracker.Options.AutoSaveLookAtEvents)
- {
- tracker.AutoTracker.LatestViewAction.Invoke();
- }
+ tracker.AutoTracker.LatestViewAction.Invoke();
}
-
- return true;
}
- return false;
- }
-
- ///
- /// Marks all of the rewards for the light world dungeons
- ///
- private void UpdateLightWorldRewards()
- {
- if (_tracker == null || _lightWorldUpdated) return;
- var rewards = new List();
- var dungeons = new (Region Region, ItemType Map)[] {
- (World.EasternPalace, ItemType.MapEP),
- (World.DesertPalace, ItemType.MapDP),
- (World.TowerOfHera, ItemType.MapTH)
- };
+ return true;
+ }
+ return false;
+ }
- foreach (var (region, map) in dungeons)
- {
- if (World.Config.ZeldaKeysanity && !Items.IsTracked(map))
- continue;
+ ///
+ /// Marks all of the rewards for the light world dungeons
+ ///
+ private void UpdateLightWorldRewards()
+ {
+ if (_tracker == null || _lightWorldUpdated) return;
- 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 = new List();
+ var dungeons = new (Region Region, ItemType Map)[] {
+ (World.EasternPalace, ItemType.MapEP),
+ (World.DesertPalace, ItemType.MapDP),
+ (World.TowerOfHera, ItemType.MapTH)
+ };
- if (!World.Config.ZeldaKeysanity && rewards.Count(x => x == RewardType.CrystalRed || x == RewardType.CrystalBlue) == 3)
- {
- _tracker.SayOnce(x => x.AutoTracker.LightWorldAllCrystals);
- }
- else if (rewards.Count == 0)
- {
- _tracker.Say(x => x.AutoTracker.LookedAtNothing);
- }
+ foreach (var (region, map) in dungeons)
+ {
+ if (World.Config.ZeldaKeysanity && !Items.IsTracked(map))
+ continue;
- // 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)
+ var dungeon = (IDungeon)region;
+ var rewardRegion = (IHasReward)region;
+ if (dungeon.DungeonState.MarkedReward != dungeon.DungeonState.Reward)
{
- _lightWorldUpdated = true;
+ rewards.Add(rewardRegion.RewardType);
+ _tracker.SetDungeonReward(dungeon, rewardRegion.RewardType);
}
+ }
+ if (!World.Config.ZeldaKeysanity && rewards.Count(x => x == RewardType.CrystalRed || x == RewardType.CrystalBlue) == 3)
+ {
+ _tracker.SayOnce(x => x.AutoTracker.LightWorldAllCrystals);
+ }
+ else if (rewards.Count == 0)
+ {
+ _tracker.Say(x => x.AutoTracker.LookedAtNothing);
}
- ///
- /// Marks all of the rewards for the dark world dungeons
- ///
- 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 (_tracker == null || _darkWorldUpdated) return;
-
- var rewards = new List();
- var dungeons = new (Region Region, ItemType Map)[] {
- (World.PalaceOfDarkness, ItemType.MapPD),
- (World.SwampPalace, ItemType.MapSP),
- (World.SkullWoods, ItemType.MapSW),
- (World.ThievesTown, ItemType.MapTT),
- (World.IcePalace, ItemType.MapIP),
- (World.MiseryMire, ItemType.MapMM),
- (World.TurtleRock, ItemType.MapTR)
- };
-
- foreach (var (region, map) in dungeons)
- {
- if (World.Config.ZeldaKeysanity && !Items.IsTracked(map))
- continue;
+ _lightWorldUpdated = true;
+ }
- 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;
+ ///
+ /// Marks all of the rewards for the dark world dungeons
+ ///
+ protected void UpdateDarkWorldRewards()
+ {
+ if (_tracker == null || _darkWorldUpdated) return;
+
+ var rewards = new List();
+ var dungeons = new (Region Region, ItemType Map)[] {
+ (World.PalaceOfDarkness, ItemType.MapPD),
+ (World.SwampPalace, ItemType.MapSP),
+ (World.SkullWoods, ItemType.MapSW),
+ (World.ThievesTown, ItemType.MapTT),
+ (World.IcePalace, ItemType.MapIP),
+ (World.MiseryMire, ItemType.MapMM),
+ (World.TurtleRock, ItemType.MapTR)
+ };
+
+ foreach (var (region, map) in dungeons)
+ {
+ if (World.Config.ZeldaKeysanity && !Items.IsTracked(map))
+ continue;
- if (!World.Config.ZeldaKeysanity && isMiseryMirePendant && isTurtleRockPendant)
- {
- _tracker.SayOnce(x => x.AutoTracker.DarkWorldNoMedallions);
- }
- else if (rewards.Count == 0)
+ var dungeon = (IDungeon)region;
+ var rewardRegion = (IHasReward)region;
+ if (dungeon.DungeonState.MarkedReward != dungeon.DungeonState.Reward)
{
- _tracker.Say(x => x.AutoTracker.LookedAtNothing);
+ rewards.Add(rewardRegion.Reward.Type);
+ _tracker.SetDungeonReward(dungeon, rewardRegion.Reward.Type);
}
+ }
- // 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)
- {
- _darkWorldUpdated = true;
- }
+ var isMiseryMirePendant = (World.MiseryMire as IDungeon).IsPendantDungeon;
+ var isTurtleRockPendant = (World.TurtleRock as IDungeon).IsPendantDungeon;
+ if (!World.Config.ZeldaKeysanity && isMiseryMirePendant && isTurtleRockPendant)
+ {
+ _tracker.SayOnce(x => x.AutoTracker.DarkWorldNoMedallions);
+ }
+ else if (rewards.Count == 0)
+ {
+ _tracker.Say(x => x.AutoTracker.LookedAtNothing);
}
+
+ // 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)
+ {
+ _darkWorldUpdated = true;
+ }
+
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs
index 139cfd58a..c875bf0c8 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs
@@ -1,6 +1,7 @@
-using Randomizer.Data.WorldData;
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
+using Randomizer.Data.WorldData;
using Randomizer.SMZ3.Contracts;
-using Randomizer.SMZ3.Tracking.Services;
namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
@@ -9,7 +10,7 @@ namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
///
public class ViewedMedallion : IZeldaStateCheck
{
- private Tracker? _tracker;
+ private ITracker? _tracker;
private readonly IWorldAccessor _worldAccessor;
private bool _mireUpdated;
private bool _turtleRockUpdated;
@@ -31,7 +32,7 @@ public ViewedMedallion(IWorldAccessor worldAccessor, IItemService itemService)
/// The current state in Zelda
/// The previous state in Zelda
/// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
if (tracker.AutoTracker == null || tracker.AutoTracker.LatestViewAction?.IsValid == true || (_mireUpdated && _turtleRockUpdated)) return false;
diff --git a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs
index 10bd38256..6a598fe00 100644
--- a/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs
+++ b/src/Randomizer.SMZ3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs
@@ -1,59 +1,57 @@
-using System;
-using System.Linq;
+using Randomizer.Abstractions;
+using Randomizer.Data.Tracking;
using Randomizer.Data.WorldData.Regions;
-using Randomizer.SMZ3.Tracking.Services;
-namespace Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks
+namespace Randomizer.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 : IZeldaStateCheck
{
+ public ZeldaDeath(IItemService itemService)
+ {
+ Items = itemService;
+ }
+
+ public IItemService Items { get; }
+
///
- /// Zelda State check for when dying
- /// Checks if the player is in the death spiral animation without a fairy
+ /// Executes the check for the current state
///
- public class ZeldaDeath : IZeldaStateCheck
+ /// The tracker instance
+ /// The current state in Zelda
+ /// The previous state in Zelda
+ /// True if the check was identified, false otherwise
+ public bool ExecuteCheck(ITracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
{
- public ZeldaDeath(IItemService itemService)
- {
- Items = itemService;
- }
+ if (tracker.AutoTracker == null || currentState.State != 0x12 || prevState.State == 0x12 || tracker.AutoTracker.PlayerHasFairy) return false;
- public IItemService Items { get; }
+ var silent = tracker.GameService!.PlayerRecentlyKilled;
- ///
- /// Executes the check for the current state
- ///
- /// The tracker instance
- /// The current state in Zelda
- /// The previous state in Zelda
- /// True if the check was identified, false otherwise
- public bool ExecuteCheck(Tracker tracker, AutoTrackerZeldaState currentState, AutoTrackerZeldaState prevState)
+ // Say specific message for dying in the particular screen/room the player is in
+ if (!silent && tracker.CurrentRegion is { WhenDiedInRoom: not null })
{
- if (tracker.AutoTracker == null || currentState.State != 0x12 || prevState.State == 0x12 || tracker.AutoTracker.PlayerHasFairy) return false;
-
- var silent = tracker.GameService!.PlayerRecentlyKilled;
-
- // Say specific message for dying in the particular screen/room the player is in
- if (!silent && tracker.CurrentRegion is { WhenDiedInRoom: not null })
+ var region = tracker.CurrentRegion.GetRegion(tracker.World) as Z3Region;
+ if (region is { IsOverworld: true } && tracker.CurrentRegion.WhenDiedInRoom.TryGetValue(prevState.OverworldScreen, out var locationResponse))
{
- var region = tracker.CurrentRegion.GetRegion(tracker.World) as Z3Region;
- if (region is { IsOverworld: true } && tracker.CurrentRegion.WhenDiedInRoom.TryGetValue(prevState.OverworldScreen, out var locationResponse))
- {
- tracker.Say(locationResponse);
- }
- else if (region is { IsOverworld: false } && tracker.CurrentRegion.WhenDiedInRoom.TryGetValue(prevState.CurrentRoom, out locationResponse))
- {
- tracker.Say(locationResponse);
- }
+ tracker.Say(locationResponse);
}
-
- tracker.TrackDeath(true);
-
- var death = Items.FirstOrDefault("Death");
- if (death is not null)
+ else if (region is { IsOverworld: false } && tracker.CurrentRegion.WhenDiedInRoom.TryGetValue(prevState.CurrentRoom, out locationResponse))
{
- tracker.TrackItem(death, autoTracked: true, silent: silent);
- return true;
+ tracker.Say(locationResponse);
}
- return false;
}
+
+ tracker.TrackDeath(true);
+
+ var death = Items.FirstOrDefault("Death");
+ if (death is not null)
+ {
+ tracker.TrackItem(death, autoTracked: true, silent: silent);
+ return true;
+ }
+ return false;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/BossTrackedEventArgs.cs b/src/Randomizer.SMZ3.Tracking/BossTrackedEventArgs.cs
deleted file mode 100644
index 203863cf9..000000000
--- a/src/Randomizer.SMZ3.Tracking/BossTrackedEventArgs.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using Randomizer.Data.WorldData;
-
-namespace Randomizer.SMZ3.Tracking
-{
- ///
- /// Provides data for events that occur when clearing a location.
- ///
- public class BossTrackedEventArgs : TrackerEventArgs
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The boss that was tracked.
- /// The speech recognition confidence.
- /// If the location was automatically tracked
- public BossTrackedEventArgs(Boss? boss, float? confidence, bool autoTracked)
- : base(confidence, autoTracked)
- {
- Boss = boss;
- }
-
- ///
- /// Gets the boss that was tracked.
- ///
- public Boss? Boss { get; }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/DungeonTrackedEventArgs.cs b/src/Randomizer.SMZ3.Tracking/DungeonTrackedEventArgs.cs
deleted file mode 100644
index 401c68a1f..000000000
--- a/src/Randomizer.SMZ3.Tracking/DungeonTrackedEventArgs.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using Randomizer.Data.WorldData;
-using Randomizer.Data.WorldData.Regions;
-
-namespace Randomizer.SMZ3.Tracking
-{
- ///
- /// Provides data for events that occur when tracking a dungeon.
- ///
- public class DungeonTrackedEventArgs : TrackerEventArgs
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The dungeon that was tracked.
- /// The speech recognition confidence.
- /// If the location was automatically tracked
- public DungeonTrackedEventArgs(IDungeon? dungeon, float? confidence, bool autoTracked)
- : base(confidence, autoTracked)
- {
- Dungeon = dungeon;
- }
-
- ///
- /// Gets the boss that was tracked.
- ///
- public IDungeon? Dungeon { get; }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/HistoryService.cs b/src/Randomizer.SMZ3.Tracking/HistoryService.cs
deleted file mode 100644
index a37fad324..000000000
--- a/src/Randomizer.SMZ3.Tracking/HistoryService.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using Microsoft.Extensions.Logging;
-using Randomizer.Data.WorldData;
-using Randomizer.Shared.Enums;
-using Randomizer.Shared.Models;
-using Randomizer.SMZ3.Contracts;
-using Randomizer.SMZ3.Tracking.Services;
-using Randomizer.SMZ3.Tracking.VoiceCommands;
-
-namespace Randomizer.SMZ3.Tracking
-{
- ///
- /// Service for managing the history of events through a playthrough
- ///
- public class HistoryService : IHistoryService
- {
- private readonly ILogger _logger;
- private readonly IWorldAccessor _world;
- private readonly ITrackerTimerService _timerService;
- private ICollection _historyEvents => _world.World.State?.History ?? new List();
- private bool _isMultiworld;
-
- ///
- /// Constructor
- ///
- ///
- ///
- ///
- public HistoryService(IWorldAccessor world, ILogger logger, ITrackerTimerService timerService)
- {
- _world = world;
- _logger = logger;
- _timerService = timerService;
- _isMultiworld = world.World.Config.MultiWorld;
- }
-
- ///
- /// Adds an event to the history log
- ///
- /// The type of event
- /// If this is an important event or not
- /// The name of the event being logged
- /// The optional location of where this event happened
- /// The created event
- public TrackerHistoryEvent AddEvent(HistoryEventType type, bool isImportant, string objectName, Location? location = null)
- {
- if (_world.World.State == null)
- {
- throw new InvalidOperationException("World tracker state not loaded");
- }
-
- var regionName = location?.Region.Name;
- var locationName = location?.Room != null ? $"{location.Room.Name} - {location.Name}" : location?.Name;
- var addedEvent = new TrackerHistoryEvent()
- {
- TrackerState = _world.World.State,
- Type = type,
- IsImportant = isImportant,
- ObjectName = objectName,
- LocationName = location != null ? $"{regionName} - {locationName}" : null,
- LocationId = location?.Id,
- Time = _timerService.SecondsElapsed
- };
- AddEvent(addedEvent);
- return addedEvent;
- }
-
- ///
- /// Adds an event to the history log
- ///
- /// The event to add
- public void AddEvent(TrackerHistoryEvent histEvent)
- {
- if (_isMultiworld) return;
- _historyEvents.Add(histEvent);
- }
-
- ///
- /// Removes the event that was added last to the log
- ///
- public void RemoveLastEvent()
- {
- if (_isMultiworld) return;
- if (_historyEvents.Count > 0)
- {
- Remove(_historyEvents.OrderByDescending(x => x.Id).First());
- }
- }
-
- ///
- /// Removes a specific event from the log
- ///
- /// The event to log
- public void Remove(TrackerHistoryEvent histEvent)
- {
- if (_isMultiworld) return;
- _historyEvents.Remove(histEvent);
- }
-
- ///
- /// Retrieves the current history log
- ///
- /// The collection of events
- public IReadOnlyCollection GetHistory() => _isMultiworld ? new List() : _historyEvents.ToList();
-
- ///
- /// Creates the progression log based off of the history
- ///
- /// The rom that the history is for
- /// All of the events to log
- /// If only important events should be logged or not
- /// The generated log text
- public static string GenerateHistoryText(GeneratedRom rom, IReadOnlyCollection history, bool importantOnly = true)
- {
- var log = new StringBuilder();
- log.AppendLine(Underline($"SMZ3 Cas’ run progression log", '='));
- log.AppendLine($"Generated on {DateTime.Now:F}");
- log.AppendLine($"Seed: {rom.Seed}");
- log.AppendLine($"Settings String: {rom.Settings}");
- log.AppendLine();
-
- var enumType = typeof(HistoryEventType);
-
- foreach (var historyEvent in history.Where(x => !x.IsUndone && (!importantOnly || x.IsImportant)).OrderBy(x => x.Time))
- {
- var time = TimeSpan.FromSeconds(historyEvent.Time).ToString(@"hh\:mm\:ss");
-
- var field = enumType.GetField(Enum.GetName(historyEvent.Type) ?? "");
- var verb = field?.GetCustomAttribute()?.Description;
-
- if (historyEvent.LocationId.HasValue)
- {
- log.AppendLine($"[{time}] {verb} {historyEvent.ObjectName} from {historyEvent.LocationName}");
- }
- else
- {
- log.AppendLine($"[{time}] {verb} {historyEvent.ObjectName}");
- }
- }
-
- var finalTime = TimeSpan.FromSeconds(rom.TrackerState?.SecondsElapsed ?? 0).ToString(@"hh\:mm\:ss");
-
- log.AppendLine();
- log.AppendLine($"Final time: {finalTime}");
-
- return log.ToString();
- }
-
- ///
- /// Underlines text in the spoiler log
- ///
- /// The text to be underlined
- /// The character to use for underlining
- /// The text to be underlined followed by the underlining text
- private static string Underline(string text, char line = '-')
- => text + "\n" + new string(line, text.Length);
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/IHistoryService.cs b/src/Randomizer.SMZ3.Tracking/IHistoryService.cs
deleted file mode 100644
index cccc345f5..000000000
--- a/src/Randomizer.SMZ3.Tracking/IHistoryService.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Speech.Recognition;
-using Microsoft.Extensions.Logging;
-using Randomizer.Data.WorldData;
-using Randomizer.Shared.Enums;
-using Randomizer.Shared.Models;
-using Randomizer.Data.Configuration.ConfigTypes;
-
-namespace Randomizer.SMZ3.Tracking.VoiceCommands
-{
- ///
- /// Service for managing the history of events through a playthrough
- ///
- public interface IHistoryService
- {
- ///
- /// Adds an event to the history log
- ///
- /// The type of event
- /// If this is an important event or not
- /// The name of the event being logged
- /// The optional location of where this event happened
- /// The created event
- public TrackerHistoryEvent AddEvent(HistoryEventType type, bool isImportant, string objectName, Location? location = null);
-
- ///
- /// Adds an event to the history log
- ///
- /// The event to add
- public void AddEvent(TrackerHistoryEvent histEvent);
-
- ///
- /// Removes the event that was added last to the log
- ///
- public void RemoveLastEvent();
-
- ///
- /// Removes a specific event from the log
- ///
- /// The event to log
- public void Remove(TrackerHistoryEvent histEvent);
-
- ///
- /// Retrieves the current history log
- ///
- /// The collection of events
- public IReadOnlyCollection GetHistory();
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/ItemTrackedEventArgs.cs b/src/Randomizer.SMZ3.Tracking/ItemTrackedEventArgs.cs
deleted file mode 100644
index 2a980cf21..000000000
--- a/src/Randomizer.SMZ3.Tracking/ItemTrackedEventArgs.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using Randomizer.Data.WorldData;
-
-namespace Randomizer.SMZ3.Tracking
-{
- ///
- /// Contains event data for item tracking events.
- ///
- public class ItemTrackedEventArgs : TrackerEventArgs
- {
- ///
- /// Initializes a new instance of the
- /// class.
- ///
- /// The item that was tracked or untracked
- ///
- /// The name of the item that was tracked.
- ///
- /// The speech recognition confidence.
- /// If the item was auto tracked
- public ItemTrackedEventArgs(Item? item, string? trackedAs, float? confidence, bool autoTracked)
- : base(confidence, autoTracked)
- {
- Item = item;
- TrackedAs = trackedAs;
- }
-
- ///
- /// Gets the name of the item as it was tracked.
- ///
- public string? TrackedAs { get; }
-
- ///
- /// The item that was tracked or untracked
- ///
- public Item? Item { get; }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/LocationClearedEventArgs.cs b/src/Randomizer.SMZ3.Tracking/LocationClearedEventArgs.cs
deleted file mode 100644
index 9c65fbe9a..000000000
--- a/src/Randomizer.SMZ3.Tracking/LocationClearedEventArgs.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using System;
-using Randomizer.Data.WorldData;
-
-namespace Randomizer.SMZ3.Tracking
-{
- ///
- /// Provides data for events that occur when clearing a location.
- ///
- public class LocationClearedEventArgs : TrackerEventArgs
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The location that was cleared.
- /// The speech recognition confidence.
- /// If the location was automatically tracked
- public LocationClearedEventArgs(Location location, float? confidence, bool autoTracked)
- : base(confidence, autoTracked)
- {
- Location = location;
- }
-
- ///
- /// Gets the location that was cleared.
- ///
- public Location Location { get; }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/Randomizer.SMZ3.Tracking.csproj b/src/Randomizer.SMZ3.Tracking/Randomizer.SMZ3.Tracking.csproj
index c31586aaa..72b11237f 100644
--- a/src/Randomizer.SMZ3.Tracking/Randomizer.SMZ3.Tracking.csproj
+++ b/src/Randomizer.SMZ3.Tracking/Randomizer.SMZ3.Tracking.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/Randomizer.SMZ3.Tracking/Services/HistoryService.cs b/src/Randomizer.SMZ3.Tracking/Services/HistoryService.cs
new file mode 100644
index 000000000..84099637b
--- /dev/null
+++ b/src/Randomizer.SMZ3.Tracking/Services/HistoryService.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
+using Randomizer.Data.WorldData;
+using Randomizer.Shared.Enums;
+using Randomizer.Shared.Models;
+using Randomizer.SMZ3.Contracts;
+
+namespace Randomizer.SMZ3.Tracking.Services;
+
+///
+/// Service for managing the history of events through a playthrough
+///
+public class HistoryService : IHistoryService
+{
+ private readonly ILogger _logger;
+ private readonly IWorldAccessor _world;
+ private readonly ITrackerTimerService _timerService;
+ private ICollection _historyEvents => _world.World.State?.History ?? new List();
+ private bool _isMultiworld;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ ///
+ public HistoryService(IWorldAccessor world, ILogger logger, ITrackerTimerService timerService)
+ {
+ _world = world;
+ _logger = logger;
+ _timerService = timerService;
+ _isMultiworld = world.World.Config.MultiWorld;
+ }
+
+ ///
+ /// Adds an event to the history log
+ ///
+ /// The type of event
+ /// If this is an important event or not
+ /// The name of the event being logged
+ /// The optional location of where this event happened
+ /// The created event
+ public TrackerHistoryEvent AddEvent(HistoryEventType type, bool isImportant, string objectName, Location? location = null)
+ {
+ if (_world.World.State == null)
+ {
+ throw new InvalidOperationException("World tracker state not loaded");
+ }
+
+ var regionName = location?.Region.Name;
+ var locationName = location?.Room != null ? $"{location.Room.Name} - {location.Name}" : location?.Name;
+ var addedEvent = new TrackerHistoryEvent()
+ {
+ TrackerState = _world.World.State,
+ Type = type,
+ IsImportant = isImportant,
+ ObjectName = objectName,
+ LocationName = location != null ? $"{regionName} - {locationName}" : null,
+ LocationId = location?.Id,
+ Time = _timerService.SecondsElapsed
+ };
+ AddEvent(addedEvent);
+ return addedEvent;
+ }
+
+ ///
+ /// Adds an event to the history log
+ ///
+ /// The event to add
+ public void AddEvent(TrackerHistoryEvent histEvent)
+ {
+ if (_isMultiworld) return;
+ _historyEvents.Add(histEvent);
+ }
+
+ ///
+ /// Removes the event that was added last to the log
+ ///
+ public void RemoveLastEvent()
+ {
+ if (_isMultiworld) return;
+ if (_historyEvents.Count > 0)
+ {
+ Remove(_historyEvents.OrderByDescending(x => x.Id).First());
+ }
+ }
+
+ ///
+ /// Removes a specific event from the log
+ ///
+ /// The event to log
+ public void Remove(TrackerHistoryEvent histEvent)
+ {
+ if (_isMultiworld) return;
+ _historyEvents.Remove(histEvent);
+ }
+
+ ///
+ /// Retrieves the current history log
+ ///
+ /// The collection of events
+ public IReadOnlyCollection GetHistory() => _isMultiworld ? new List() : _historyEvents.ToList();
+
+ ///
+ /// Creates the progression log based off of the history
+ ///
+ /// The rom that the history is for
+ /// All of the events to log
+ /// If only important events should be logged or not
+ /// The generated log text
+ public static string GenerateHistoryText(GeneratedRom rom, IReadOnlyCollection history, bool importantOnly = true)
+ {
+ var log = new StringBuilder();
+ log.AppendLine(Underline($"SMZ3 Cas’ run progression log", '='));
+ log.AppendLine($"Generated on {DateTime.Now:F}");
+ log.AppendLine($"Seed: {rom.Seed}");
+ log.AppendLine($"Settings String: {rom.Settings}");
+ log.AppendLine();
+
+ var enumType = typeof(HistoryEventType);
+
+ foreach (var historyEvent in history.Where(x => !x.IsUndone && (!importantOnly || x.IsImportant)).OrderBy(x => x.Time))
+ {
+ var time = TimeSpan.FromSeconds(historyEvent.Time).ToString(@"hh\:mm\:ss");
+
+ var field = enumType.GetField(Enum.GetName(historyEvent.Type) ?? "");
+ var verb = field?.GetCustomAttribute()?.Description;
+
+ if (historyEvent.LocationId.HasValue)
+ {
+ log.AppendLine($"[{time}] {verb} {historyEvent.ObjectName} from {historyEvent.LocationName}");
+ }
+ else
+ {
+ log.AppendLine($"[{time}] {verb} {historyEvent.ObjectName}");
+ }
+ }
+
+ var finalTime = TimeSpan.FromSeconds(rom.TrackerState?.SecondsElapsed ?? 0).ToString(@"hh\:mm\:ss");
+
+ log.AppendLine();
+ log.AppendLine($"Final time: {finalTime}");
+
+ return log.ToString();
+ }
+
+ ///
+ /// Underlines text in the spoiler log
+ ///
+ /// The text to be underlined
+ /// The character to use for underlining
+ /// The text to be underlined followed by the underlining text
+ private static string Underline(string text, char line = '-')
+ => text + "\n" + new string(line, text.Length);
+}
diff --git a/src/Randomizer.SMZ3.Tracking/Services/IItemService.cs b/src/Randomizer.SMZ3.Tracking/Services/IItemService.cs
deleted file mode 100644
index 373f5b94e..000000000
--- a/src/Randomizer.SMZ3.Tracking/Services/IItemService.cs
+++ /dev/null
@@ -1,169 +0,0 @@
-using System.Collections.Generic;
-using Randomizer.Data.WorldData;
-using Randomizer.Shared;
-using Randomizer.Data.Configuration.ConfigTypes;
-using Randomizer.Data.WorldData.Regions;
-
-namespace Randomizer.SMZ3.Tracking.Services
-{
- ///
- /// 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/Randomizer.SMZ3.Tracking/Services/ItemService.cs b/src/Randomizer.SMZ3.Tracking/Services/ItemService.cs
index 6ae7b9aab..0f66c90c8 100644
--- a/src/Randomizer.SMZ3.Tracking/Services/ItemService.cs
+++ b/src/Randomizer.SMZ3.Tracking/Services/ItemService.cs
@@ -1,287 +1,288 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Randomizer.Abstractions;
using Randomizer.Data.WorldData;
using Randomizer.Shared;
using Randomizer.Data.Configuration.ConfigFiles;
using Randomizer.Data.Configuration.ConfigTypes;
using Randomizer.SMZ3.Contracts;
using Randomizer.Data.WorldData.Regions;
+using Randomizer.Shared.Enums;
-namespace Randomizer.SMZ3.Tracking.Services
+namespace Randomizer.SMZ3.Tracking.Services;
+
+///
+/// Manages items and their tracking state.
+///
+public class ItemService : IItemService
{
+ private readonly IWorldAccessor _world;
+ private readonly Dictionary _progression = new();
+
///
- /// Manages items and their tracking state.
+ /// Initializes a new instance of the class
+ /// with the specified dependencies.
///
- public class ItemService : IItemService
+ ///
+ /// 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)
{
- 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;
- }
+ 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))
+ ?? 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;
- ///
- /// 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))
- ?? 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)
+ ///
+ /// 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))
{
- var item = FirstOrDefault(itemType);
- return item?.Metadata.NameWithArticle ?? itemType.GetDescription();
+ return _progression[key];
}
+ var progression = new Progression();
- ///
- /// 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)
+ if (!_world.World.Config.MetroidKeysanity || assumeKeys)
{
- var reward = FirstOrDefault(rewardType);
- return reward?.Metadata.NameWithArticle ?? rewardType.GetDescription();
+ progression.AddRange(_world.World.ItemPools.Keycards);
+ if (assumeKeys)
+ progression.AddRange(_world.World.ItemPools.Dungeon);
}
- ///
- /// 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)
+ foreach (var item in TrackedItems().Select(x => x.State).Distinct())
{
- 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;
+ if (item.Type == null || item.Type == ItemType.Nothing) continue;
+ progression.AddRange(Enumerable.Repeat(item.Type.Value, item.TrackingState));
}
- ///
- /// 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)
+ foreach (var reward in TrackedRewards())
{
- 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);
- }
+ progression.Add(reward);
}
- ///
- /// Clears the progression cache after collecting new items, rewards, or bosses
- ///
- public void ResetProgression()
+ foreach (var boss in TrackedBosses())
{
- _progression.Clear();
+ 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);
+ }
+ }
- // TODO: Tracking methods
+ ///
+ /// Clears the progression cache after collecting new items, rewards, or bosses
+ ///
+ public void ResetProgression()
+ {
+ _progression.Clear();
}
+
+
+ // TODO: Tracking methods
}
diff --git a/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs b/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs
index 96f00caf8..a4888c52c 100644
--- a/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs
+++ b/src/Randomizer.SMZ3.Tracking/Services/WorldService.cs
@@ -2,6 +2,7 @@
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
+using Randomizer.Abstractions;
using Randomizer.Data.WorldData;
using Randomizer.Data.WorldData.Regions;
using Randomizer.Shared;
diff --git a/src/Randomizer.SMZ3.Tracking/Tracker.cs b/src/Randomizer.SMZ3.Tracking/Tracker.cs
index 63daffd4a..9f5bf533c 100644
--- a/src/Randomizer.SMZ3.Tracking/Tracker.cs
+++ b/src/Randomizer.SMZ3.Tracking/Tracker.cs
@@ -4,7 +4,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Speech.Recognition;
-using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -12,12 +11,12 @@
using BunLabs;
using Microsoft.Extensions.Logging;
using MSURandomizerLibrary.Configs;
+using Randomizer.Abstractions;
using Randomizer.Shared;
using Randomizer.Shared.Enums;
using Randomizer.Shared.Models;
using Randomizer.SMZ3.ChatIntegration;
using Randomizer.SMZ3.Contracts;
-using Randomizer.SMZ3.Tracking.AutoTracking;
using Randomizer.Data.Configuration;
using Randomizer.Data.Configuration.ConfigFiles;
using Randomizer.Data.Configuration.ConfigTypes;
@@ -30,1183 +29,1128 @@
using Randomizer.Data;
using Randomizer.Data.Options;
using Randomizer.Data.Services;
+using Randomizer.Data.Tracking;
-namespace Randomizer.SMZ3.Tracking
+namespace Randomizer.SMZ3.Tracking;
+
+///
+/// Tracks items and locations in a playthrough by listening for voice
+/// commands and responding with text-to-speech.
+///
+public class Tracker : ITracker, IDisposable
{
+ private const int RepeatRateModifier = 2;
+ private static readonly Random s_random = new();
+
+
+ private readonly IWorldAccessor _worldAccessor;
+ private readonly TrackerModuleFactory _moduleFactory;
+ private readonly IChatClient _chatClient;
+ private readonly ILogger _logger;
+ private readonly TrackerOptionsAccessor _trackerOptions;
+ private readonly Dictionary _idleTimers;
+ private readonly Stack<(Action Action, DateTime UndoTime)> _undoHistory = new();
+ private readonly RandomizerContext _dbContext;
+ private readonly ICommunicator _communicator;
+ private readonly ITrackerStateService _stateService;
+ private readonly IWorldService _worldService;
+ private readonly ITrackerTimerService _timerService;
+ private readonly ISpeechRecognitionService _recognizer;
+ private bool _disposed;
+ private string? _mood;
+ private string? _lastSpokenText;
+ private Dictionary _progression = new();
+ private readonly bool _alternateTracker;
+ private readonly HashSet _saidLines = new();
+ private bool _beatenGame;
+ private IEnumerable? _previousMissingItems;
+
///
- /// Tracks items and locations in a playthrough by listening for voice
- /// commands and responding with text-to-speech.
- ///
- public class Tracker : IDisposable
- {
- private const int RepeatRateModifier = 2;
- private static readonly Random s_random = new();
-
-
- private readonly IWorldAccessor _worldAccessor;
- private readonly TrackerModuleFactory _moduleFactory;
- private readonly IChatClient _chatClient;
- private readonly ILogger _logger;
- private readonly TrackerOptionsAccessor _trackerOptions;
- private readonly Dictionary _idleTimers;
- private readonly Stack<(Action Action, DateTime UndoTime)> _undoHistory = new();
- private readonly RandomizerContext _dbContext;
- private readonly ICommunicator _communicator;
- private readonly ITrackerStateService _stateService;
- private readonly IWorldService _worldService;
- private readonly ITrackerTimerService _timerService;
- private readonly ISpeechRecognitionService _recognizer;
- private bool _disposed;
- private string? _mood;
- private string? _lastSpokenText;
- private Dictionary _progression = new();
- private readonly bool _alternateTracker;
- private readonly HashSet _saidLines = new();
- private bool _beatenGame;
- private IEnumerable? _previousMissingItems;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Used to access external configuration.
- ///
- /// Used to get the world to track in.
- ///
- ///
- /// Used to provide the tracking speech recognition syntax.
- ///
- ///
- /// Used to write logging information.
- /// Provides Tracker preferences.
- /// The database context
- ///
- ///
- /// Service for
- ///
- ///
- ///
- ///
- ///
- public Tracker(ConfigProvider configProvider,
- IWorldAccessor worldAccessor,
- TrackerModuleFactory moduleFactory,
- IChatClient chatClient,
- ILogger logger,
- TrackerOptionsAccessor trackerOptions,
- RandomizerContext dbContext,
- IItemService itemService,
- ICommunicator communicator,
- IHistoryService historyService,
- Configs configs,
- IMetadataService metadataService,
- ITrackerStateService stateService,
- IWorldService worldService,
- ITrackerTimerService timerService,
- ISpeechRecognitionService speechRecognitionService)
- {
- if (trackerOptions.Options == null)
- throw new InvalidOperationException("Tracker options have not yet been activated.");
-
- _worldAccessor = worldAccessor;
- _moduleFactory = moduleFactory;
- _chatClient = chatClient;
- _logger = logger;
- _trackerOptions = trackerOptions;
- _dbContext = dbContext;
- ItemService = itemService;
- _communicator = communicator;
- _stateService = stateService;
- _worldService = worldService;
- _timerService = timerService;
-
- // Initialize the tracker configuration
- Responses = configs.Responses;
- Requests = configs.Requests;
- Metadata = metadataService;
- ItemService.ResetProgression();
+ /// Initializes a new instance of the class.
+ ///
+ /// Used to access external configuration.
+ ///
+ /// Used to get the world to track in.
+ ///
+ ///
+ /// Used to provide the tracking speech recognition syntax.
+ ///
+ ///
+ /// Used to write logging information.
+ /// Provides Tracker preferences.
+ /// The database context
+ ///
+ ///
+ /// Service for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Tracker(ConfigProvider configProvider,
+ IWorldAccessor worldAccessor,
+ TrackerModuleFactory moduleFactory,
+ IChatClient chatClient,
+ ILogger logger,
+ TrackerOptionsAccessor trackerOptions,
+ RandomizerContext dbContext,
+ IItemService itemService,
+ ICommunicator communicator,
+ IHistoryService historyService,
+ Configs configs,
+ IMetadataService metadataService,
+ ITrackerStateService stateService,
+ IWorldService worldService,
+ ITrackerTimerService timerService,
+ ISpeechRecognitionService speechRecognitionService)
+ {
+ if (trackerOptions.Options == null)
+ throw new InvalidOperationException("Tracker options have not yet been activated.");
+
+ _worldAccessor = worldAccessor;
+ _moduleFactory = moduleFactory;
+ _chatClient = chatClient;
+ _logger = logger;
+ _trackerOptions = trackerOptions;
+ _dbContext = dbContext;
+ ItemService = itemService;
+ _communicator = communicator;
+ _stateService = stateService;
+ _worldService = worldService;
+ _timerService = timerService;
+
+ // Initialize the tracker configuration
+ Responses = configs.Responses;
+ Requests = configs.Requests;
+ Metadata = metadataService;
+ ItemService.ResetProgression();
+
+ History = historyService;
+
+ // Initalize the timers used to trigger idle responses
+ _idleTimers = Responses.Idle.ToDictionary(
+ x => x.Key,
+ x => new Timer(IdleTimerElapsed, x.Key, Timeout.Infinite, Timeout.Infinite));
+
+ // Initialize the text-to-speech
+ if (s_random.NextDouble() <= 0.01)
+ {
+ _alternateTracker = true;
+ _communicator.UseAlternateVoice();
+ }
+
+ // Initialize the speech recognition engine
+ _recognizer = speechRecognitionService;
+ _recognizer.SpeechRecognized += Recognizer_SpeechRecognized;
+ InitializeMicrophone();
+ }
- History = historyService;
+ ///
+ /// Occurs when any speech was recognized, regardless of configured
+ /// thresholds.
+ ///
+ public event EventHandler? SpeechRecognized;
- // Initalize the timers used to trigger idle responses
- _idleTimers = Responses.Idle.ToDictionary(
- x => x.Key,
- x => new Timer(IdleTimerElapsed, x.Key, Timeout.Infinite, Timeout.Infinite));
+ ///
+ /// Occurs when one more more items have been tracked.
+ ///
+ public event EventHandler? ItemTracked;
- // Initialize the text-to-speech
- if (s_random.NextDouble() <= 0.01)
- {
- _alternateTracker = true;
- _communicator.UseAlternateVoice();
- }
+ ///
+ /// Occurs when a location has been cleared.
+ ///
+ public event EventHandler? LocationCleared;
- // Initialize the speech recognition engine
- _recognizer = speechRecognitionService;
- _recognizer.SpeechRecognized += Recognizer_SpeechRecognized;
- InitializeMicrophone();
- }
-
- ///
- /// Occurs when any speech was recognized, regardless of configured
- /// thresholds.
- ///
- public event EventHandler? SpeechRecognized;
-
- ///
- /// Occurs when one more 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 the last action was undone.
- ///
- public event EventHandler? ActionUndone;
-
- ///
- /// Occurs when the tracker state has been loaded.
- ///
- public event EventHandler? StateLoaded;
-
- ///
- /// 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 the current played track number is updated
- ///
- public event EventHandler? TrackNumberUpdated;
-
- ///
- /// Occurs when the current track has changed
- ///
- public event EventHandler? TrackChanged;
-
- ///
- /// Set when the progression needs to be updated for the current tracker
- /// instance
- ///
- public bool UpdateTrackerProgression { get; set; }
-
- ///
- /// Gets extra information about locations.
- ///
- public IMetadataService Metadata { get; }
-
- ///
- /// Gets a reference to the .
- ///
- public IItemService ItemService { get; }
-
- ///
- /// The number of pegs that have been pegged for Peg World mode
- ///
- public int PegsPegged { get; set; }
-
- ///
- /// Gets the world for the currently tracked playthrough.
- ///
- public World World => _worldAccessor.World;
-
- ///
- /// Indicates whether Tracker is in Go Mode.
- ///
- public bool GoMode { get; private set; }
-
- ///
- /// Indicates whether Tracker is in Peg World mode.
- ///
- public bool PegWorldMode { get; set; }
-
- ///
- /// Indicates whether Tracker is in Shaktool mode.
- ///
- public bool ShaktoolMode { get; set; }
-
- ///
- /// If the speech recognition engine was fully initialized
- ///
- public bool MicrophoneInitialized { get; private set; }
-
- ///
- /// If voice recognition has been enabled or not
- ///
- public bool VoiceRecognitionEnabled { get; private set; }
-
- ///
- /// Gets the configured responses.
- ///
- public ResponseConfig Responses { get; }
-
- ///
- /// Gets a collection of basic requests and responses.
- ///
- public IReadOnlyCollection Requests { get; }
-
- ///
- /// Gets a dictionary containing the rules and the various speech
- /// recognition syntaxes.
- ///
- public IReadOnlyDictionary> Syntax { get; private set; }
- = new Dictionary>();
-
- ///
- /// Gets the tracking preferences.
- ///
- public TrackerOptions Options => _trackerOptions.Options!;
-
- ///
- /// The generated rom
- ///
- public GeneratedRom? Rom { get; private set; }
-
- ///
- /// The path to the generated rom
- ///
- public string? RomPath { get; private set; }
-
- ///
- /// The region the player is currently in according to the Auto Tracker
- ///
- public RegionInfo? CurrentRegion { get; private set; }
-
- ///
- /// The map to display for the player
- ///
- public string CurrentMap { get; private set; } = "";
-
- ///
- /// The current track number being played
- ///
- public int CurrentTrackNumber { get; private set; }
-
- ///
- /// Gets a string describing tracker's mood.
- ///
- public string Mood
- {
- get => _mood ??= Responses.Moods.Keys.Random(Rng.Current) ?? Responses.Moods.Keys.First();
- }
-
- ///
- /// Get if the Tracker has been updated since it was last saved
- ///
- public bool IsDirty { get; set; }
-
- ///
- /// The Auto Tracker for the Tracker
- ///
- public AutoTracker? AutoTracker { get; set; }
-
- ///
- /// Service that handles modifying the game via auto tracker
- ///
- public GameService? GameService { get; set; }
-
- ///
- /// Module that houses the history
- ///
- public IHistoryService History { get; set; }
-
- ///
- /// Gets or sets a value indicating whether Tracker may give hints when
- /// asked about items or locations.
- ///
- public bool HintsEnabled { get; set; }
-
- ///
- /// Gets or sets a value indicating whether Tracker may give spoilers
- /// when asked about items or locations.
- ///
- public bool SpoilersEnabled { get; set; }
-
- ///
- /// Gets if the local player has beaten the game or not
- ///
- public bool HasBeatenGame => _beatenGame;
-
- ///
- /// Formats a string so that it will be pronounced correctly by the
- /// text-to-speech engine.
- ///
- /// The text to correct.
- /// A string with the pronunciations replaced.
- public static string CorrectPronunciation(string name)
- => name.Replace("Samus", "Sammus");
-
- ///
- /// Attempts to replace a user name with a pronunciation-corrected
- /// version of it.
- ///
- /// The user name to correct.
- ///
- /// The corrected user name, or .
- ///
- public string CorrectUserNamePronunciation(string userName)
- {
- var correctedUserName = Responses.Chat.UserNamePronunciation
- .SingleOrDefault(x => x.Key.Equals(userName, StringComparison.OrdinalIgnoreCase));
-
- return string.IsNullOrEmpty(correctedUserName.Value) ? userName.Replace('_', ' ') : correctedUserName.Value;
- }
-
- ///
- /// Initializes the microphone from the default audio device
- ///
- ///
- /// True if the microphone is initialized, false otherwise
- ///
- public bool InitializeMicrophone()
- {
- if (MicrophoneInitialized) return true;
- MicrophoneInitialized = _recognizer.InitializeMicrophone();
- return MicrophoneInitialized;
- }
-
- ///
- /// Loads the state from the database for a given rom
- ///
- /// The rom to load
- /// The full path to the rom to load
- /// True or false if the load was successful
- public bool Load(GeneratedRom rom, string romPath)
- {
- IsDirty = false;
- Rom = rom;
- RomPath = romPath;
- var trackerState = _stateService.LoadState(_worldAccessor.Worlds, rom);
-
- if (trackerState != null)
- {
- _timerService.SetSavedTime(TimeSpan.FromSeconds(trackerState.SecondsElapsed));
- OnStateLoaded();
- return true;
- }
- return false;
- }
+ ///
+ /// Occurs when Peg World mode has been toggled on.
+ ///
+ public event EventHandler? ToggledPegWorldModeOn;
- ///
- /// Saves the state of the tracker to the database
- ///
- ///
- public async Task SaveAsync()
- {
- if (Rom == null)
- {
- throw new NullReferenceException("No rom loaded into tracker");
- }
- IsDirty = false;
- await _stateService.SaveStateAsync(_worldAccessor.Worlds, Rom, _timerService.SecondsElapsed);
- }
+ ///
+ /// Occurs when going to Shaktool
+ ///
+ public event EventHandler? ToggledShaktoolMode;
- ///
- /// Undoes the last operation.
- ///
- /// The speech recognition confidence.
- public void Undo(float confidence)
- {
- if (_undoHistory.TryPop(out var undoLast))
- {
- if ((DateTime.Now - undoLast.UndoTime).TotalMinutes <= (_trackerOptions.Options?.UndoExpirationTime ?? 3))
- {
- Say(Responses.ActionUndone);
- undoLast.Action();
- OnActionUndone(new TrackerEventArgs(confidence));
- }
- else
- {
- Say(Responses.UndoExpired);
- _undoHistory.Push(undoLast);
- }
- }
- else
- {
- Say(Responses.NothingToUndo);
- }
- }
+ ///
+ /// Occurs when a Peg World peg has been pegged.
+ ///
+ public event EventHandler? PegPegged;
- ///
- /// Toggles Go Mode on.
- ///
- /// The speech recognition confidence.
- public void ToggleGoMode(float? confidence = null)
- {
- ShutUp();
- Say("Toggled Go Mode ", wait: true);
- GoMode = true;
- OnGoModeToggledOn(new TrackerEventArgs(confidence));
- Say("on.");
+ ///
+ /// Occurs when the properties of a dungeon have changed.
+ ///
+ public event EventHandler? DungeonUpdated;
- AddUndo(() =>
- {
- GoMode = false;
- if (Responses.GoModeToggledOff != null)
- Say(Responses.GoModeToggledOff);
- });
- }
+ ///
+ /// Occurs when the properties of a boss have changed.
+ ///
+ public event EventHandler? BossUpdated;
- ///
- /// 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 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(Responses.DungeonTooManyTreasuresTracked?.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonState.RemainingTreasure, amount));
- return false;
- }
+ ///
+ /// Occurs when the marked locations have changed
+ ///
+ public event EventHandler? MarkedLocationsUpdated;
- if (dungeon.DungeonState.RemainingTreasure > 0)
- {
- dungeon.DungeonState.RemainingTreasure -= amount;
+ ///
+ /// Occurs when Go mode has been turned on.
+ ///
+ public event EventHandler? GoModeToggledOn;
- // If there are no more treasures and the boss is defeated, clear all locations in the dungeon
- var clearedLocations = new List();
- if (dungeon.DungeonState.RemainingTreasure == 0 && dungeon.DungeonState.Cleared)
- {
- 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);
- }
- }
+ ///
+ /// Occurs when the last action was undone.
+ ///
+ public event EventHandler? ActionUndone;
- // 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))
- Say(response.Format(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))
- Say(response.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonState.RemainingTreasure));
- }
+ ///
+ /// Occurs when the tracker state has been loaded.
+ ///
+ public event EventHandler? StateLoaded;
- OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
- AddUndo(() =>
- {
- dungeon.DungeonState.RemainingTreasure += amount;
- foreach (var location in clearedLocations)
- {
- location.State.Cleared = false;
- }
- });
+ ///
+ /// Occurs when the map has been updated
+ ///
+ public event EventHandler? MapUpdated;
- return true;
- }
- else if (stateResponse && confidence != null && Responses.DungeonTreasureTracked.TryGetValue(-1, out var response))
- {
- // Attempted to track treasure when all treasure items were
- // already cleared out
- Say(response.Format(dungeon.DungeonMetadata.Name));
- }
+ ///
+ /// Occurs when the map has been updated
+ ///
+ public event EventHandler? BeatGame;
- return false;
- }
+ ///
+ /// Occurs when the map has died
+ ///
+ public event EventHandler? PlayerDied;
- ///
- /// 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 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(Responses.DungeonRewardMarked.Format(dungeon.DungeonMetadata.Name, rewardObj?.Metadata.Name ?? reward.GetDescription()));
- }
+ ///
+ /// Occurs when the current played track number is updated
+ ///
+ public event EventHandler? TrackNumberUpdated;
- OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
+ ///
+ /// Occurs when the current track has changed
+ ///
+ public event EventHandler? TrackChanged;
+
+ ///
+ /// Set when the progression needs to be updated for the current tracker
+ /// instance
+ ///
+ public bool UpdateTrackerProgression { get; set; }
- if (!autoTracked) AddUndo(() => dungeon.DungeonState.MarkedReward = originalReward);
+ ///
+ /// Gets extra information about locations.
+ ///
+ public IMetadataService Metadata { get; }
+
+ ///
+ /// Gets a reference to the .
+ ///
+ public IItemService ItemService { get; }
+
+ ///
+ /// The number of pegs that have been pegged for Peg World mode
+ ///
+ public int PegsPegged { get; set; }
+
+ ///
+ /// Gets the world for the currently tracked playthrough.
+ ///
+ public World World => _worldAccessor.World;
+
+ ///
+ /// Indicates whether Tracker is in Go Mode.
+ ///
+ public bool GoMode { get; private set; }
+
+ ///
+ /// Indicates whether Tracker is in Peg World mode.
+ ///
+ public bool PegWorldMode { get; set; }
+
+ ///
+ /// Indicates whether Tracker is in Shaktool mode.
+ ///
+ public bool ShaktoolMode { get; set; }
+
+ ///
+ /// If the speech recognition engine was fully initialized
+ ///
+ public bool MicrophoneInitialized { get; private set; }
+
+ ///
+ /// If voice recognition has been enabled or not
+ ///
+ public bool VoiceRecognitionEnabled { get; private set; }
+
+ ///
+ /// Gets the configured responses.
+ ///
+ public ResponseConfig Responses { get; }
+
+ ///
+ /// Gets a collection of basic requests and responses.
+ ///
+ public IReadOnlyCollection Requests { get; }
+
+ ///
+ /// Gets a dictionary containing the rules and the various speech
+ /// recognition syntaxes.
+ ///
+ public IReadOnlyDictionary> Syntax { get; private set; }
+ = new Dictionary>();
+
+ ///
+ /// Gets the tracking preferences.
+ ///
+ public TrackerOptions Options => _trackerOptions.Options!;
+
+ ///
+ /// The generated rom
+ ///
+ public GeneratedRom? Rom { get; private set; }
+
+ ///
+ /// The path to the generated rom
+ ///
+ public string? RomPath { get; private set; }
+
+ ///
+ /// The region the player is currently in according to the Auto Tracker
+ ///
+ public RegionInfo? CurrentRegion { get; private set; }
+
+ ///
+ /// The map to display for the player
+ ///
+ public string CurrentMap { get; private set; } = "";
+
+ ///
+ /// The current track number being played
+ ///
+ public int CurrentTrackNumber { get; private set; }
+
+ ///
+ /// Gets a string describing tracker's mood.
+ ///
+ public string Mood
+ {
+ get => _mood ??= Responses.Moods.Keys.Random(Rng.Current) ?? Responses.Moods.Keys.First();
+ }
+
+ ///
+ /// Get if the Tracker has been updated since it was last saved
+ ///
+ public bool IsDirty { get; set; }
+
+ ///
+ /// The Auto Tracker for the Tracker
+ ///
+ public IAutoTracker? AutoTracker { get; set; }
+
+ ///
+ /// Service that handles modifying the game via auto tracker
+ ///
+ public IGameService? GameService { get; set; }
+
+ ///
+ /// Module that houses the history
+ ///
+ public IHistoryService History { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether Tracker may give hints when
+ /// asked about items or locations.
+ ///
+ public bool HintsEnabled { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether Tracker may give spoilers
+ /// when asked about items or locations.
+ ///
+ public bool SpoilersEnabled { get; set; }
+
+ ///
+ /// Gets if the local player has beaten the game or not
+ ///
+ public bool HasBeatenGame => _beatenGame;
+
+ ///
+ /// Attempts to replace a user name with a pronunciation-corrected
+ /// version of it.
+ ///
+ /// The user name to correct.
+ ///
+ /// The corrected user name, or .
+ ///
+ public string CorrectUserNamePronunciation(string userName)
+ {
+ var correctedUserName = Responses.Chat.UserNamePronunciation
+ .SingleOrDefault(x => x.Key.Equals(userName, StringComparison.OrdinalIgnoreCase));
+
+ return string.IsNullOrEmpty(correctedUserName.Value) ? userName.Replace('_', ' ') : correctedUserName.Value;
+ }
+
+ ///
+ /// Initializes the microphone from the default audio device
+ ///
+ ///
+ /// True if the microphone is initialized, false otherwise
+ ///
+ public bool InitializeMicrophone()
+ {
+ if (MicrophoneInitialized) return true;
+ MicrophoneInitialized = _recognizer.InitializeMicrophone();
+ return MicrophoneInitialized;
+ }
+
+ ///
+ /// Loads the state from the database for a given rom
+ ///
+ /// The rom to load
+ /// The full path to the rom to load
+ /// True or false if the load was successful
+ public bool Load(GeneratedRom rom, string romPath)
+ {
+ IsDirty = false;
+ Rom = rom;
+ RomPath = romPath;
+ var trackerState = _stateService.LoadState(_worldAccessor.Worlds, rom);
+
+ if (trackerState != null)
+ {
+ _timerService.SetSavedTime(TimeSpan.FromSeconds(trackerState.SecondsElapsed));
+ OnStateLoaded();
+ return true;
}
+ return false;
+ }
- ///
- /// Sets the reward of all unmarked dungeons.
- ///
- /// The reward to set.
- /// The speech recognition confidence.
- public void SetUnmarkedDungeonReward(RewardType reward, float? confidence = null)
+ ///
+ /// Saves the state of the tracker to the database
+ ///
+ ///
+ public async Task SaveAsync()
+ {
+ if (Rom == null)
{
- var unmarkedDungeons = World.Dungeons
- .Where(x => x.DungeonState is { HasReward: true, HasMarkedReward: false })
- .ToImmutableList();
+ throw new NullReferenceException("No rom loaded into tracker");
+ }
+ IsDirty = false;
+ await _stateService.SaveStateAsync(_worldAccessor.Worlds, Rom, _timerService.SecondsElapsed);
+ }
- if (unmarkedDungeons.Count > 0)
+ ///
+ /// Undoes the last operation.
+ ///
+ /// The speech recognition confidence.
+ public void Undo(float confidence)
+ {
+ if (_undoHistory.TryPop(out var undoLast))
+ {
+ if ((DateTime.Now - undoLast.UndoTime).TotalMinutes <= (_trackerOptions.Options?.UndoExpirationTime ?? 3))
{
- Say(Responses.RemainingDungeonsMarked.Format(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));
+ Say(Responses.ActionUndone);
+ undoLast.Action();
+ OnActionUndone(new TrackerEventArgs(confidence));
}
else
{
- Say(Responses.NoRemainingDungeons);
+ Say(Responses.UndoExpired);
+ _undoHistory.Push(undoLast);
}
}
-
- ///
- /// Sets the dungeon's medallion requirement to the specified item.
- ///
- /// The dungeon to mark.
- /// The medallion that is required.
- /// The speech recognition confidence.
- public void SetDungeonRequirement(IDungeon dungeon, ItemType? medallion = null, float? confidence = null)
+ else
{
- var region = World.Regions.SingleOrDefault(x => dungeon.DungeonMetadata.Name.Contains(x.Name, StringComparison.OrdinalIgnoreCase));
- if (region == null)
- {
- Say("Strange, I can't find that dungeon in this seed.");
- }
- else if (region is not INeedsMedallion medallionRegion)
- {
- Say(Responses.DungeonRequirementInvalid.Format(dungeon.DungeonMetadata.Name));
- return;
- }
+ Say(Responses.NothingToUndo);
+ }
+ }
- 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(Responses.DungeonRequirementMismatch?.Format(
- HintsEnabled ? "a different medallion" : medallionRegion.Medallion.ToString(),
- dungeon.DungeonMetadata.Name,
- medallion.Value.ToString()));
- }
+ ///
+ /// Toggles Go Mode on.
+ ///
+ /// The speech recognition confidence.
+ public void ToggleGoMode(float? confidence = null)
+ {
+ ShutUp();
+ Say("Toggled Go Mode ", wait: true);
+ GoMode = true;
+ OnGoModeToggledOn(new TrackerEventArgs(confidence));
+ Say("on.");
+
+ AddUndo(() =>
+ {
+ GoMode = false;
+ if (Responses.GoModeToggledOff != null)
+ Say(Responses.GoModeToggledOff);
+ });
+ }
- dungeon.DungeonState.MarkedMedallion = medallion.Value;
- Say(Responses.DungeonRequirementMarked.Format(medallion.ToString(), dungeon.DungeonMetadata.Name));
- OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, false));
- }
+ ///
+ /// 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 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.");
- AddUndo(() => dungeon.DungeonState.MarkedMedallion = originalRequirement);
+ 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(Responses.DungeonTooManyTreasuresTracked?.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonState.RemainingTreasure, amount));
+ return false;
}
- ///
- /// Starts voice recognition.
- ///
- public virtual bool TryStartTracking()
+ if (dungeon.DungeonState.RemainingTreasure > 0)
{
- // Load the modules for voice recognition
- StartTimer(true);
+ dungeon.DungeonState.RemainingTreasure -= amount;
- var loadError = false;
- try
- {
- Syntax = _moduleFactory.LoadAll(this, _recognizer.RecognitionEngine, out loadError);
- }
- catch (Exception e)
+ // If there are no more treasures and the boss is defeated, clear all locations in the dungeon
+ var clearedLocations = new List();
+ if (dungeon.DungeonState.RemainingTreasure == 0 && dungeon.DungeonState.Cleared)
{
- _logger.LogError(e, "Unable to load modules");
- loadError = 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);
+ }
}
- try
- {
- EnableVoiceRecognition();
- }
- catch (InvalidOperationException e)
+ // 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))
{
- _logger.LogError(e, "Error enabling voice recognition");
- loadError = true;
+ // Try to get the response based on the amount of items left
+ if (Responses.DungeonTreasureTracked.TryGetValue(dungeon.DungeonState.RemainingTreasure, out var response))
+ Say(response.Format(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))
+ Say(response.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonState.RemainingTreasure));
}
- Say(_alternateTracker ? Responses.StartingTrackingAlternate : Responses.StartedTracking);
- RestartIdleTimers();
- return !loadError;
- }
-
- ///
- /// Connects Tracker to chat.
- ///
- /// The user name to connect as.
- ///
- /// The OAuth token for .
- ///
- ///
- /// The channel to monitor for incoming messages.
- ///
- ///
- /// The is for .
- ///
- public void ConnectToChat(string? userName, string? oauthToken, string? channel, string? id)
- {
- if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(oauthToken))
+ OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
+ AddUndo(() =>
{
- try
- {
- _chatClient.Connect(userName, oauthToken, channel ?? userName, id ?? "");
- }
- catch (AggregateException e)
+ dungeon.DungeonState.RemainingTreasure += amount;
+ foreach (var location in clearedLocations)
{
- _logger.LogError(e, "Error in connection to Twitch chat");
- Say(x => x.Chat.WhenDisconnected);
+ location.State.Cleared = false;
}
- }
+ });
+
+ return true;
+ }
+ else if (stateResponse && confidence != null && Responses.DungeonTreasureTracked.TryGetValue(-1, out var response))
+ {
+ // Attempted to track treasure when all treasure items were
+ // already cleared out
+ Say(response.Format(dungeon.DungeonMetadata.Name));
}
- ///
- /// Sets the start time of the timer
- ///
- public virtual void StartTimer(bool isInitial = false)
+ 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 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
{
- _timerService.StartTimer();
+ dungeon.DungeonState.MarkedReward = reward.Value;
+ var rewardObj = ItemService.FirstOrDefault(reward.Value);
+ Say(Responses.DungeonRewardMarked.Format(dungeon.DungeonMetadata.Name, rewardObj?.Metadata.Name ?? reward.GetDescription()));
+ }
- if (!isInitial)
- {
- Say(Responses.TimerResumed);
- AddUndo(() => _timerService.Undo());
- }
+ OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
+
+ if (!autoTracked) AddUndo(() => dungeon.DungeonState.MarkedReward = originalReward);
+ }
+
+ ///
+ /// Sets the reward of all unmarked dungeons.
+ ///
+ /// The reward to set.
+ /// The speech recognition confidence.
+ public 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(Responses.RemainingDungeonsMarked.Format(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));
}
+ else
+ {
+ Say(Responses.NoRemainingDungeons);
+ }
+ }
- ///
- /// Resets the timer to 0
- ///
- public virtual void ResetTimer(bool isInitial = false)
+ ///
+ /// Sets the dungeon's medallion requirement to the specified item.
+ ///
+ /// The dungeon to mark.
+ /// The medallion that is required.
+ /// The speech recognition confidence.
+ public void SetDungeonRequirement(IDungeon dungeon, ItemType? medallion = null, float? confidence = null)
+ {
+ var region = World.Regions.SingleOrDefault(x => dungeon.DungeonMetadata.Name.Contains(x.Name, StringComparison.OrdinalIgnoreCase));
+ if (region == null)
{
- _timerService.ResetTimer();
+ Say("Strange, I can't find that dungeon in this seed.");
+ }
+ else if (region is not INeedsMedallion medallionRegion)
+ {
+ Say(Responses.DungeonRequirementInvalid.Format(dungeon.DungeonMetadata.Name));
+ return;
+ }
- if (!isInitial)
+ 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(Responses.TimerReset);
- AddUndo(() => _timerService.Undo());
+ Say(Responses.DungeonRequirementMismatch?.Format(
+ HintsEnabled ? "a different medallion" : medallionRegion.Medallion.ToString(),
+ dungeon.DungeonMetadata.Name,
+ medallion.Value.ToString()));
}
+
+ dungeon.DungeonState.MarkedMedallion = medallion.Value;
+ Say(Responses.DungeonRequirementMarked.Format(medallion.ToString(), dungeon.DungeonMetadata.Name));
+ OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, false));
}
- ///
- /// Pauses the timer, saving the elapsed time
- ///
- public virtual Action? PauseTimer(bool addUndo = true)
- {
- _timerService.StopTimer();
+ AddUndo(() => dungeon.DungeonState.MarkedMedallion = originalRequirement);
+ }
- Say(Responses.TimerPaused);
+ ///
+ /// Starts voice recognition.
+ ///
+ public virtual bool TryStartTracking()
+ {
+ // Load the modules for voice recognition
+ StartTimer(true);
- if (addUndo)
- {
- AddUndo(() => _timerService.Undo());
- return null;
- }
- else
- {
- return () => _timerService.Undo();
- }
+ var loadError = false;
+ try
+ {
+ Syntax = _moduleFactory.LoadAll(this, _recognizer.RecognitionEngine, out loadError);
}
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Unable to load modules");
+ loadError = true;
+ }
+
+ try
+ {
+ EnableVoiceRecognition();
+ }
+ catch (InvalidOperationException e)
+ {
+ _logger.LogError(e, "Error enabling voice recognition");
+ loadError = true;
+ }
+
+ Say(_alternateTracker ? Responses.StartingTrackingAlternate : Responses.StartedTracking);
+ RestartIdleTimers();
+ return !loadError;
+ }
- ///
- /// Pauses or resumes the timer based on if it is
- /// currently paused or not
- ///
- public virtual void ToggleTimer()
+ ///
+ /// Connects Tracker to chat.
+ ///
+ /// The user name to connect as.
+ ///
+ /// The OAuth token for .
+ ///
+ ///
+ /// The channel to monitor for incoming messages.
+ ///
+ ///
+ /// The is for .
+ ///
+ public void ConnectToChat(string? userName, string? oauthToken, string? channel, string? id)
+ {
+ if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(oauthToken))
{
- if (_timerService.IsTimerPaused)
+ try
{
- StartTimer();
+ _chatClient.Connect(userName, oauthToken, channel ?? userName, id ?? "");
}
- else
+ catch (AggregateException e)
{
- PauseTimer();
+ _logger.LogError(e, "Error in connection to Twitch chat");
+ Say(x => x.Chat.WhenDisconnected);
}
}
+ }
- ///
- /// Stops voice recognition.
- ///
- public virtual void StopTracking()
- {
- DisableVoiceRecognition();
- _communicator.Abort();
- _chatClient.Disconnect();
- Say(GoMode ? Responses.StoppedTrackingPostGoMode : Responses.StoppedTracking, wait: true);
+ ///
+ /// Sets the start time of the timer
+ ///
+ public virtual void StartTimer(bool isInitial = false)
+ {
+ _timerService.StartTimer();
- foreach (var timer in _idleTimers.Values)
- timer.Change(Timeout.Infinite, Timeout.Infinite);
+ if (!isInitial)
+ {
+ Say(Responses.TimerResumed);
+ AddUndo(() => _timerService.Undo());
}
+ }
+
+ ///
+ /// Resets the timer to 0
+ ///
+ public virtual void ResetTimer(bool isInitial = false)
+ {
+ _timerService.ResetTimer();
- ///
- /// Enables the voice recognizer if the microphone is enabled
- ///
- public void EnableVoiceRecognition()
+ if (!isInitial)
{
- if (MicrophoneInitialized && !VoiceRecognitionEnabled)
- {
- _logger.LogInformation("Starting speech recognition");
- _recognizer.SetInputToDefaultAudioDevice();
- _recognizer.RecognizeAsyncStop();
- _recognizer.RecognizeAsync(RecognizeMode.Multiple);
- VoiceRecognitionEnabled = true;
- }
+ Say(Responses.TimerReset);
+ AddUndo(() => _timerService.Undo());
}
+ }
+
+ ///
+ /// Pauses the timer, saving the elapsed time
+ ///
+ public virtual Action? PauseTimer(bool addUndo = true)
+ {
+ _timerService.StopTimer();
+
+ Say(Responses.TimerPaused);
- ///
- /// Disables voice recognition if it was previously enabled
- ///
- public void DisableVoiceRecognition()
+ if (addUndo)
{
- if (VoiceRecognitionEnabled)
- {
- VoiceRecognitionEnabled = false;
- _recognizer.RecognizeAsyncStop();
- _logger.LogInformation("Stopped speech recognition");
- }
+ AddUndo(() => _timerService.Undo());
+ return null;
}
-
- ///
- /// Speak a sentence using text-to-speech.
- ///
- /// The possible sentences to speak.
- ///
- /// true if a sentence was spoken, false if was null.
- ///
- public virtual bool Say(SchrodingersString? text)
+ else
{
- if (text == null)
- return false;
-
- return Say(text.ToString());
+ return () => _timerService.Undo();
}
+ }
- ///
- /// Speak a sentence using text-to-speech.
- ///
- /// Selects the response to use.
- ///
- /// true if a sentence was spoken, false if the selected
- /// response was null.
- ///
- public virtual bool Say(Func selectResponse)
+ ///
+ /// Pauses or resumes the timer based on if it is
+ /// currently paused or not
+ ///
+ public virtual void ToggleTimer()
+ {
+ if (_timerService.IsTimerPaused)
{
- return Say(selectResponse(Responses));
+ StartTimer();
}
-
- ///
- /// Speak a sentence using text-to-speech.
- ///
- /// The possible sentences to speak.
- /// The arguments used to format the text.
- ///
- /// true if a sentence was spoken, false if was null.
- ///
- public virtual bool Say(SchrodingersString? text, params object?[] args)
+ else
{
- if (text == null)
- return false;
-
- return Say(text.Format(args), wait: false);
+ PauseTimer();
}
+ }
+
+ ///
+ /// Stops voice recognition.
+ ///
+ public virtual void StopTracking()
+ {
+ DisableVoiceRecognition();
+ _communicator.Abort();
+ _chatClient.Disconnect();
+ Say(GoMode ? Responses.StoppedTrackingPostGoMode : Responses.StoppedTracking, wait: true);
- ///
- /// Speak a sentence using text-to-speech.
- ///
- /// Selects the response to use.
- /// The arguments used to format the text.
- ///
- /// true if a sentence was spoken, false if the selected
- /// response was null.
- ///
- public virtual bool Say(Func selectResponse, params object?[] args)
+ foreach (var timer in _idleTimers.Values)
+ timer.Change(Timeout.Infinite, Timeout.Infinite);
+ }
+
+ ///
+ /// Enables the voice recognizer if the microphone is enabled
+ ///
+ public void EnableVoiceRecognition()
+ {
+ if (MicrophoneInitialized && !VoiceRecognitionEnabled)
{
- return Say(selectResponse(Responses), args);
+ _logger.LogInformation("Starting speech recognition");
+ _recognizer.SetInputToDefaultAudioDevice();
+ _recognizer.RecognizeAsyncStop();
+ _recognizer.RecognizeAsync(RecognizeMode.Multiple);
+ VoiceRecognitionEnabled = true;
}
+ }
- ///
- /// Speak a sentence using text-to-speech only one time.
- ///
- /// The possible sentences to speak.
- ///
- /// true if a sentence was spoken, false if was null.
- ///
- public virtual bool SayOnce(SchrodingersString? text)
+ ///
+ /// Disables voice recognition if it was previously enabled
+ ///
+ public void DisableVoiceRecognition()
+ {
+ if (VoiceRecognitionEnabled)
{
- if (text == null)
- return false;
+ VoiceRecognitionEnabled = false;
+ _recognizer.RecognizeAsyncStop();
+ _logger.LogInformation("Stopped speech recognition");
+ }
+ }
- if (!_saidLines.Contains(text))
- {
- _saidLines.Add(text);
- return Say(text.ToString());
- }
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// The possible sentences to speak.
+ ///
+ /// true if a sentence was spoken, false if was null.
+ ///
+ public virtual bool Say(SchrodingersString? text)
+ {
+ if (text == null)
+ return false;
- return true;
- }
+ return Say(text.ToString());
+ }
- ///
- /// Speak a sentence using text-to-speech only one time.
- ///
- /// Selects the response to use.
- ///
- /// true if a sentence was spoken, false if the selected
- /// response was null.
- ///
- public virtual bool SayOnce(Func selectResponse)
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// Selects the response to use.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public virtual bool Say(Func selectResponse)
+ {
+ return Say(selectResponse(Responses));
+ }
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// The possible sentences to speak.
+ /// The arguments used to format the text.
+ ///
+ /// true if a sentence was spoken, false if was null.
+ ///
+ public virtual bool Say(SchrodingersString? text, params object?[] args)
+ {
+ if (text == null)
+ return false;
+
+ return Say(text.Format(args), wait: false);
+ }
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// Selects the response to use.
+ /// The arguments used to format the text.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public virtual bool Say(Func selectResponse, params object?[] args)
+ {
+ return Say(selectResponse(Responses), args);
+ }
+
+ ///
+ /// Speak a sentence using text-to-speech only one time.
+ ///
+ /// The possible sentences to speak.
+ ///
+ /// true if a sentence was spoken, false if was null.
+ ///
+ public virtual bool SayOnce(SchrodingersString? text)
+ {
+ if (text == null)
+ return false;
+
+ if (!_saidLines.Contains(text))
{
- return SayOnce(selectResponse(Responses));
+ _saidLines.Add(text);
+ return Say(text.ToString());
}
- ///
- /// Speak a sentence using text-to-speech only one time.
- ///
- /// The possible sentences to speak.
- /// The arguments used to format the text.
- ///
- /// true if a sentence was spoken, false if was null.
- ///
- protected virtual bool SayOnce(SchrodingersString? text, params object?[] args)
- {
- if (text == null)
- return false;
+ return true;
+ }
- if (!_saidLines.Contains(text))
- {
- _saidLines.Add(text);
- return Say(text.Format(args), wait: false);
- }
+ ///
+ /// Speak a sentence using text-to-speech only one time.
+ ///
+ /// Selects the response to use.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public virtual bool SayOnce(Func selectResponse)
+ {
+ return SayOnce(selectResponse(Responses));
+ }
- return true;
- }
+ ///
+ /// Speak a sentence using text-to-speech only one time.
+ ///
+ /// The possible sentences to speak.
+ /// The arguments used to format the text.
+ ///
+ /// true if a sentence was spoken, false if was null.
+ ///
+ protected virtual bool SayOnce(SchrodingersString? text, params object?[] args)
+ {
+ if (text == null)
+ return false;
- ///
- /// Speak a sentence using text-to-speech only one time.
- ///
- /// Selects the response to use.
- /// The arguments used to format the text.
- ///
- /// true if a sentence was spoken, false if the selected
- /// response was null.
- ///
- public virtual bool SayOnce(Func selectResponse, params object?[] args)
- {
- return SayOnce(selectResponse(Responses), args);
- }
-
- ///
- /// Speak a sentence using text-to-speech.
- ///
- /// The phrase to speak.
- ///
- /// true to wait until the text has been spoken completely or
- /// false to immediately return. The default is false.
- ///
- ///
- /// true if a sentence was spoken, false if the selected
- /// response was null.
- ///
- public virtual bool Say(string? text, bool wait = false)
- {
- if (text == null)
- return false;
-
- var formattedText = FormatPlaceholders(text);
- if (wait)
- _communicator.SayWait(formattedText);
- else
- _communicator.Say(formattedText);
- _lastSpokenText = text;
- return true;
+ if (!_saidLines.Contains(text))
+ {
+ _saidLines.Add(text);
+ return Say(text.Format(args), wait: false);
}
- ///
- /// Replaces global placeholders in a given string.
- ///
- /// The text with placeholders to format.
- /// The formatted text with placeholders replaced.
- [return: NotNullIfNotNull("text")]
- protected virtual string? FormatPlaceholders(string? text)
- {
- if (string.IsNullOrEmpty(text))
- return text;
+ return true;
+ }
- var builder = new StringBuilder(text);
- builder.Replace("{Link}", CorrectPronunciation(World.Config.LinkName));
- builder.Replace("{Samus}", CorrectPronunciation(World.Config.SamusName));
- builder.Replace("{User}", CorrectUserNamePronunciation(Options.UserName ?? "someone"));
+ ///
+ /// Speak a sentence using text-to-speech only one time.
+ ///
+ /// Selects the response to use.
+ /// The arguments used to format the text.
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public virtual bool SayOnce(Func selectResponse, params object?[] args)
+ {
+ return SayOnce(selectResponse(Responses), args);
+ }
+
+ ///
+ /// Speak a sentence using text-to-speech.
+ ///
+ /// The phrase to speak.
+ ///
+ /// true to wait until the text has been spoken completely or
+ /// false to immediately return. The default is false.
+ ///
+ ///
+ /// true if a sentence was spoken, false if the selected
+ /// response was null.
+ ///
+ public virtual bool Say(string? text, bool wait = false)
+ {
+ if (text == null)
+ return false;
+
+ var formattedText = FormatPlaceholders(text);
+ if (wait)
+ _communicator.SayWait(formattedText);
+ else
+ _communicator.Say(formattedText);
+ _lastSpokenText = text;
+ return true;
+ }
- // Just in case some text doesn't pass a string.Format
- builder.Replace("{{Link}}", CorrectPronunciation(World.Config.LinkName));
- builder.Replace("{{Samus}}", CorrectPronunciation(World.Config.SamusName));
- builder.Replace("{{User}}", CorrectUserNamePronunciation(Options.UserName ?? "someone"));
- return builder.ToString();
+ ///
+ /// Replaces global placeholders in a given string.
+ ///
+ /// The text with placeholders to format.
+ /// The formatted text with placeholders replaced.
+ [return: NotNullIfNotNull("text")]
+ protected virtual string? FormatPlaceholders(string? text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return text;
+
+ var builder = new StringBuilder(text);
+ builder.Replace("{Link}", ITracker.CorrectPronunciation(World.Config.LinkName));
+ builder.Replace("{Samus}", ITracker.CorrectPronunciation(World.Config.SamusName));
+ builder.Replace("{User}", CorrectUserNamePronunciation(Options.UserName ?? "someone"));
+
+ // Just in case some text doesn't pass a string.Format
+ builder.Replace("{{Link}}", ITracker.CorrectPronunciation(World.Config.LinkName));
+ builder.Replace("{{Samus}}", ITracker.CorrectPronunciation(World.Config.SamusName));
+ builder.Replace("{{User}}", CorrectUserNamePronunciation(Options.UserName ?? "someone"));
+ return builder.ToString();
+ }
+
+ ///
+ /// Repeats the most recently spoken sentence using text-to-speech at a
+ /// slower rate.
+ ///
+ public virtual void Repeat()
+ {
+ if (Options.VoiceFrequency == TrackerVoiceFrequency.Disabled)
+ {
+ return;
}
- ///
- /// Repeats the most recently spoken sentence using text-to-speech at a
- /// slower rate.
- ///
- public virtual void Repeat()
+ if (_lastSpokenText == null)
{
- if (Options.VoiceFrequency == TrackerVoiceFrequency.Disabled)
- {
- return;
- }
+ Say("I haven't said anything yet.");
+ return;
+ }
- if (_lastSpokenText == null)
- {
- Say("I haven't said anything yet.");
- return;
- }
+ _communicator.SayWait("I said");
+ _communicator.SlowDown();
+ Say(_lastSpokenText, wait: true);
+ _communicator.SpeedUp();
+ }
- _communicator.SayWait("I said");
- _communicator.SlowDown();
- Say(_lastSpokenText, wait: true);
- _communicator.SpeedUp();
- }
-
- ///
- /// Makes Tracker stop talking.
- ///
- public virtual void ShutUp()
- {
- _communicator.Abort();
- }
-
- ///
- /// Notifies the user an error occurred.
- ///
- public virtual void Error()
- {
- Say(Responses.Error);
- }
-
- ///
- /// Cleans up resources used by this class.
- ///
- public void Dispose()
- {
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// 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 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 = _worldService.AccessibleLocations(false);
- var itemName = item.Name;
- var originalTrackingState = item.State.TrackingState;
- ItemService.ResetProgression();
+ ///
+ /// Makes Tracker stop talking.
+ ///
+ public virtual void ShutUp()
+ {
+ _communicator.Abort();
+ }
+
+ ///
+ /// Notifies the user an error occurred.
+ ///
+ public virtual void Error()
+ {
+ Say(Responses.Error);
+ }
- 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);
+ ///
+ /// Cleans up resources used by this class.
+ ///
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
- // Actually track the item if it's for the local player's world
- if (item.World == World)
+ ///
+ /// 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 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 = _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);
+
+ // Actually track the item if it's for the local player's world
+ if (item.World == World)
+ {
+ if (item.Metadata.HasStages)
{
- if (item.Metadata.HasStages)
+ if (trackedAs != null && item.Metadata.GetStage(trackedAs) != null)
{
- if (trackedAs != null && item.Metadata.GetStage(trackedAs) != null)
- {
- var stage = item.Metadata.GetStage(trackedAs)!;
+ 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();
+ // 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)
+ didTrack = item.Track(stage.Value);
+ if (stateResponse)
+ {
+ if (didTrack)
{
- if (didTrack)
+ if (item.TryGetTrackingResponse(out var response))
{
- if (item.TryGetTrackingResponse(out var response))
- {
- Say(response.Format(item.Counter));
- }
- else
- {
- Say(Responses.TrackedItemByStage.Format(itemName, stageName));
- }
+ Say(response.Format(item.Counter));
}
else
{
- Say(Responses.TrackedOlderProgressiveItem?.Format(itemName, item.Metadata.Stages[item.State.TrackingState].ToString()));
+ Say(Responses.TrackedItemByStage.Format(itemName, stageName));
}
}
- }
- else
- {
- // Tracked by regular name, upgrade by one step
- didTrack = item.Track();
- if (stateResponse)
+ else
{
- if (didTrack)
- {
- if (item.TryGetTrackingResponse(out var response))
- {
- Say(response.Format(item.Counter));
- }
- else
- {
- var stageName = item.Metadata.Stages[item.State.TrackingState].ToString();
- Say(Responses.TrackedProgressiveItem.Format(itemName, stageName));
- }
- }
- else
- {
- Say(Responses.TrackedTooManyOfAnItem?.Format(itemName));
- }
+ Say(Responses.TrackedOlderProgressiveItem?.Format(itemName, item.Metadata.Stages[item.State.TrackingState].ToString()));
}
}
}
- else if (item.Metadata.Multiple)
- {
- didTrack = item.Track();
- if (item.TryGetTrackingResponse(out var response))
- {
- if (stateResponse)
- Say(response.Format(item.Counter));
- }
- else if (item.Counter == 1)
- {
- if (stateResponse)
- Say(Responses.TrackedItem.Format(itemName, item.Metadata.NameWithArticle));
- }
- else if (item.Counter > 1)
- {
- if (stateResponse)
- Say(Responses.TrackedItemMultiple.Format(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(Responses.TrackedItem.Format(itemName, item.Metadata.NameWithArticle));
- }
- }
else
{
+ // Tracked by regular name, upgrade by one step
didTrack = item.Track();
if (stateResponse)
{
@@ -1218,1472 +1162,1520 @@ public bool TrackItem(Item item, string? trackedAs = null, float? confidence = n
}
else
{
- Say(Responses.TrackedItem.Format(itemName, item.Metadata.NameWithArticle));
+ var stageName = item.Metadata.Stages[item.State.TrackingState].ToString();
+ Say(Responses.TrackedProgressiveItem.Format(itemName, stageName));
}
}
else
{
- Say(Responses.TrackedAlreadyTrackedItem?.Format(itemName));
+ Say(Responses.TrackedTooManyOfAnItem?.Format(itemName));
+ }
+ }
+ }
+ }
+ else if (item.Metadata.Multiple)
+ {
+ didTrack = item.Track();
+ if (item.TryGetTrackingResponse(out var response))
+ {
+ if (stateResponse)
+ Say(response.Format(item.Counter));
+ }
+ else if (item.Counter == 1)
+ {
+ if (stateResponse)
+ Say(Responses.TrackedItem.Format(itemName, item.Metadata.NameWithArticle));
+ }
+ else if (item.Counter > 1)
+ {
+ if (stateResponse)
+ Say(Responses.TrackedItemMultiple.Format(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(Responses.TrackedItem.Format(itemName, item.Metadata.NameWithArticle));
+ }
+ }
+ else
+ {
+ didTrack = item.Track();
+ if (stateResponse)
+ {
+ if (didTrack)
+ {
+ if (item.TryGetTrackingResponse(out var response))
+ {
+ Say(response.Format(item.Counter));
+ }
+ else
+ {
+ Say(Responses.TrackedItem.Format(itemName, item.Metadata.NameWithArticle));
}
}
+ else
+ {
+ Say(Responses.TrackedAlreadyTrackedItem?.Format(itemName));
+ }
}
}
+ }
- var undoTrack = () => { item.State.TrackingState = originalTrackingState; ItemService.ResetProgression(); };
- OnItemTracked(new ItemTrackedEventArgs(item, trackedAs, confidence, autoTracked));
+ var undoTrack = () => { item.State.TrackingState = originalTrackingState; ItemService.ResetProgression(); };
+ OnItemTracked(new ItemTrackedEventArgs(item, trackedAs, confidence, autoTracked));
- // Check if we can clear a location
- Action? undoClear = null;
- Action? undoTrackDungeonTreasure = null;
+ // 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();
+ }
- // If this was not gifted to the player, try to clear the location
- if (!giftedItem && item.Type != ItemType.Nothing)
+ // Clear the location if it's for the local player's world
+ if (location != null && location.World == World && location.State.Cleared == false)
{
- if (location == null && !World.Config.MultiWorld)
+ if (stateResponse)
{
- location = _worldService.Locations(outOfLogic: true, itemFilter: item.Type).TrySingle();
+ GiveLocationComment(item, location, isTracking: true, confidence);
}
- // Clear the location if it's for the local player's world
- if (location != null && location.World == World && location.State.Cleared == false)
+ if (tryClear)
{
- if (stateResponse)
- {
- GiveLocationComment(item, location, isTracking: true, confidence);
- }
+ // 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));
- if (tryClear)
+ undoClear = () => location.State.Cleared = false;
+ if ((location.State.MarkedItem ?? ItemType.Nothing) != ItemType.Nothing)
{
- // If this item was in a dungeon, track treasure count
- undoTrackDungeonTreasure = TryTrackDungeonTreasure(location, confidence, autoTracked, stateResponse);
+ location.State.MarkedItem = null;
+ OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
+ }
+ }
- // 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));
+ 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;
- undoClear = () => location.State.Cleared = false;
- if ((location.State.MarkedItem ?? ItemType.Nothing) != ItemType.Nothing)
- {
- location.State.MarkedItem = null;
- OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
- }
+ if (locationInfo.OutOfLogic != null)
+ {
+ Say(locationInfo.OutOfLogic);
}
-
- 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))
+ else if (roomInfo?.OutOfLogic != null)
+ {
+ Say(roomInfo.OutOfLogic);
+ }
+ else if (regionInfo.OutOfLogic != null)
{
- var locationInfo = location.Metadata;
- var roomInfo = location.Room?.Metadata;
- var regionInfo = location.Region.Metadata;
+ Say(regionInfo.OutOfLogic);
+ }
+ else
+ {
+ var allMissingCombinations = Logic.GetMissingRequiredItems(location, items, out var allMissingItems);
+ allMissingItems = allMissingItems.OrderBy(x => x);
- if (locationInfo.OutOfLogic != null)
- {
- Say(locationInfo.OutOfLogic);
- }
- else if (roomInfo?.OutOfLogic != null)
- {
- Say(roomInfo.OutOfLogic);
- }
- else if (regionInfo.OutOfLogic != null)
+ var missingItems = allMissingCombinations.MinBy(x => x.Length);
+ if (missingItems == null)
{
- Say(regionInfo.OutOfLogic);
+ Say(x => x.TrackedOutOfLogicItemTooManyMissing, item.Metadata.Name, locationInfo.Name);
}
+ // Do not say anything if the only thing missing are keys
else
{
- var allMissingCombinations = Logic.GetMissingRequiredItems(location, items, out var allMissingItems);
- allMissingItems = allMissingItems.OrderBy(x => x);
+ var itemsChanged = _previousMissingItems == null || !allMissingItems.SequenceEqual(_previousMissingItems);
+ var onlyKeys = allMissingItems.All(x => x.IsInAnyCategory(ItemCategory.BigKey, ItemCategory.SmallKey, ItemCategory.Keycard));
+ _previousMissingItems = allMissingItems;
- var missingItems = allMissingCombinations.MinBy(x => x.Length);
- if (missingItems == null)
+ if (itemsChanged && !onlyKeys)
{
- Say(x => x.TrackedOutOfLogicItemTooManyMissing, item.Metadata.Name, locationInfo.Name);
+ var missingItemNames = NaturalLanguage.Join(missingItems.Select(ItemService.GetName));
+ Say(x => x.TrackedOutOfLogicItem, item.Metadata.Name, locationInfo.Name, missingItemNames);
}
- // Do not say anything if the only thing missing are keys
- else
- {
- var itemsChanged = _previousMissingItems == null || !allMissingItems.SequenceEqual(_previousMissingItems);
- var onlyKeys = allMissingItems.All(x => x.IsInAnyCategory(ItemCategory.BigKey, ItemCategory.SmallKey, ItemCategory.Keycard));
- _previousMissingItems = allMissingItems;
-
- if (itemsChanged && !onlyKeys)
- {
- var missingItemNames = NaturalLanguage.Join(missingItems.Select(ItemService.GetName));
- Say(x => x.TrackedOutOfLogicItem, item.Metadata.Name, locationInfo.Name, missingItemNames);
- }
- }
-
- _previousMissingItems = allMissingItems;
}
+ _previousMissingItems = allMissingItems;
}
+
}
}
+ }
- var addedEvent = History.AddEvent(
- HistoryEventType.TrackedItem,
- item.Metadata.IsProgression(World.Config),
- item.Metadata.NameWithArticle,
- location
- );
+ var addedEvent = History.AddEvent(
+ HistoryEventType.TrackedItem,
+ item.Metadata.IsProgression(World.Config),
+ item.Metadata.NameWithArticle,
+ location
+ );
- IsDirty = true;
+ IsDirty = true;
- if (!autoTracked)
+ if (!autoTracked)
+ {
+ AddUndo(() =>
{
- AddUndo(() =>
- {
- undoTrack();
- undoClear?.Invoke();
- undoTrackDungeonTreasure?.Invoke();
- ItemService.ResetProgression();
- addedEvent.IsUndone = true;
- });
- }
+ undoTrack();
+ undoClear?.Invoke();
+ undoTrackDungeonTreasure?.Invoke();
+ ItemService.ResetProgression();
+ addedEvent.IsUndone = true;
+ });
+ }
- GiveLocationHint(accessibleBefore);
- RestartIdleTimers();
+ GiveLocationHint(accessibleBefore);
+ RestartIdleTimers();
- return didTrack;
- }
+ return didTrack;
+ }
- ///
- /// 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 void TrackItems(List- items, bool autoTracked, bool giftedItem)
+ ///
+ /// 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 void TrackItems(List
- items, bool autoTracked, bool giftedItem)
+ {
+ if (items.Count == 1)
{
- if (items.Count == 1)
- {
- TrackItem(items.First(), null, null, false, autoTracked, null, giftedItem);
- return;
- }
+ TrackItem(items.First(), null, null, false, autoTracked, null, giftedItem);
+ return;
+ }
- ItemService.ResetProgression();
+ ItemService.ResetProgression();
- foreach (var item in items)
- {
- item.Track();
- }
+ foreach (var item in items)
+ {
+ item.Track();
+ }
- if (items.Count == 2)
- {
- Say(x => x.TrackedTwoItems, items[0].Metadata.Name, items[1].Metadata.Name);
- }
- else if (items.Count == 3)
- {
- Say(x => x.TrackedThreeItems, items[0].Metadata.Name, items[1].Metadata.Name, items[2].Metadata.Name);
- }
- else
+ if (items.Count == 2)
+ {
+ Say(x => x.TrackedTwoItems, items[0].Metadata.Name, items[1].Metadata.Name);
+ }
+ else if (items.Count == 3)
+ {
+ Say(x => x.TrackedThreeItems, items[0].Metadata.Name, items[1].Metadata.Name, items[2].Metadata.Name);
+ }
+ else
+ {
+ var itemsToSay = items.Where(x => x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(2).ToList();
+ if (itemsToSay.Count() < 2)
{
- 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, itemsToSay[0].Metadata.Name, itemsToSay[1].Metadata.Name, items.Count - 2);
+ var numToTake = 2 - itemsToSay.Count();
+ itemsToSay.AddRange(items.Where(x => !x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(numToTake));
}
- OnItemTracked(new ItemTrackedEventArgs(null, null, null, true));
- IsDirty = true;
- RestartIdleTimers();
+ Say(x => x.TrackedManyItems, itemsToSay[0].Metadata.Name, itemsToSay[1].Metadata.Name, items.Count - 2);
}
- ///
- /// Removes an item from the tracker.
- ///
- /// The item to untrack.
- /// The speech recognition confidence.
- public void UntrackItem(Item item, float? confidence = null)
- {
- var originalTrackingState = item.State.TrackingState;
- ItemService.ResetProgression();
+ OnItemTracked(new ItemTrackedEventArgs(null, null, null, true));
+ IsDirty = true;
+ RestartIdleTimers();
+ }
- if (!item.Untrack())
- {
- Say(Responses.UntrackedNothing.Format(item.Name, item.Metadata.NameWithArticle));
- return;
- }
+ ///
+ /// Removes an item from the tracker.
+ ///
+ /// The item to untrack.
+ /// The speech recognition confidence.
+ public void UntrackItem(Item item, float? confidence = null)
+ {
+ var originalTrackingState = item.State.TrackingState;
+ ItemService.ResetProgression();
- if (item.Metadata.HasStages)
- {
- Say(Responses.UntrackedProgressiveItem.Format(item.Name, item.Metadata.NameWithArticle));
- }
- else if (item.Metadata.Multiple)
+ if (!item.Untrack())
+ {
+ Say(Responses.UntrackedNothing.Format(item.Name, item.Metadata.NameWithArticle));
+ return;
+ }
+
+ if (item.Metadata.HasStages)
+ {
+ Say(Responses.UntrackedProgressiveItem.Format(item.Name, item.Metadata.NameWithArticle));
+ }
+ else if (item.Metadata.Multiple)
+ {
+ if (item.State.TrackingState > 0)
{
- if (item.State.TrackingState > 0)
- {
- if (item.Metadata.CounterMultiplier > 1)
- Say(Responses.UntrackedItemMultiple.Format($"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}", $"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}"));
- else
- Say(Responses.UntrackedItemMultiple.Format(item.Name, item.Metadata.NameWithArticle));
- }
+ if (item.Metadata.CounterMultiplier > 1)
+ Say(Responses.UntrackedItemMultiple.Format($"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}", $"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}"));
else
- Say(Responses.UntrackedItemMultipleLast.Format(item.Name, item.Metadata.NameWithArticle));
+ Say(Responses.UntrackedItemMultiple.Format(item.Name, item.Metadata.NameWithArticle));
}
else
- {
- Say(Responses.UntrackedItem.Format(item.Name, item.Metadata.NameWithArticle));
- }
+ Say(Responses.UntrackedItemMultipleLast.Format(item.Name, item.Metadata.NameWithArticle));
+ }
+ else
+ {
+ Say(Responses.UntrackedItem.Format(item.Name, item.Metadata.NameWithArticle));
+ }
- IsDirty = true;
- OnItemTracked(new(item, null, confidence, false));
- AddUndo(() => { item.State.TrackingState = originalTrackingState; ItemService.ResetProgression(); });
+ IsDirty = true;
+ OnItemTracked(new(item, null, confidence, false));
+ AddUndo(() => { item.State.TrackingState = originalTrackingState; ItemService.ResetProgression(); });
+ }
+
+ ///
+ /// 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 void TrackItem(Item item, IDungeon dungeon, string? trackedAs = null, float? confidence = null)
+ {
+ 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;
}
- ///
- /// 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 void TrackItem(Item item, IDungeon dungeon, string? trackedAs = null, float? confidence = null)
+ 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)
{
- var tracked = TrackItem(item, trackedAs, confidence, tryClear: false);
- var undoTrack = _undoHistory.Pop();
- ItemService.ResetProgression();
+ location.State.Cleared = true;
+ World.LastClearedLocation = location;
+ OnLocationCleared(new(location, confidence, false));
- // 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
+ if (location.State.HasMarkedItem)
{
- dungeon = GetDungeonFromItem(item, dungeon)!;
- if (TrackDungeonTreasure(dungeon, confidence))
- undoTrackTreasure = _undoHistory.Pop().Action;
+ location.State.MarkedItem = null;
+ OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
}
- 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)
+ AddUndo(() =>
{
- location.State.Cleared = true;
- World.LastClearedLocation = location;
- OnLocationCleared(new(location, confidence, false));
+ undoTrack.Action();
+ undoTrackTreasure?.Invoke();
+ location.State.Cleared = false;
+ ItemService.ResetProgression();
+ });
+ }
+ else
+ {
+ AddUndo(() =>
+ {
+ undoTrack.Action();
+ undoTrackTreasure?.Invoke();
+ ItemService.ResetProgression();
+ });
+ }
+ }
- if (location.State.HasMarkedItem)
- {
- location.State.MarkedItem = null;
- OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
- }
+ ///
+ /// 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 void TrackItem(Item item, IHasLocations area, string? trackedAs = null, float? confidence = null)
+ {
+ var locations = area.Locations
+ .Where(x => x.Item.Type == item.Type)
+ .ToImmutableList();
+ ItemService.ResetProgression();
- AddUndo(() =>
- {
- undoTrack.Action();
- undoTrackTreasure?.Invoke();
- location.State.Cleared = false;
- ItemService.ResetProgression();
- });
- }
- else
- {
- AddUndo(() =>
- {
- undoTrack.Action();
- undoTrackTreasure?.Invoke();
- ItemService.ResetProgression();
- });
- }
+ if (locations.Count == 0)
+ {
+ Say(Responses.AreaDoesNotHaveItem?.Format(item.Name, area.Name, item.Metadata.NameWithArticle));
+ }
+ else if (locations.Count > 1)
+ {
+ // Consider tracking/clearing everything?
+ Say(Responses.AreaHasMoreThanOneItem?.Format(item.Name, area.Name, item.Metadata.NameWithArticle));
}
- ///
- /// 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 void TrackItem(Item item, IHasLocations area, string? trackedAs = null, float? confidence = null)
- {
- var locations = area.Locations
- .Where(x => x.Item.Type == item.Type)
- .ToImmutableList();
- ItemService.ResetProgression();
+ IsDirty = true;
- if (locations.Count == 0)
- {
- Say(Responses.AreaDoesNotHaveItem?.Format(item.Name, area.Name, item.Metadata.NameWithArticle));
- }
- else if (locations.Count > 1)
+ TrackItem(item, trackedAs, confidence, tryClear: false);
+ if (locations.Count == 1)
+ {
+ Clear(locations.Single());
+ var undoClear = _undoHistory.Pop();
+ var undoTrack = _undoHistory.Pop();
+ AddUndo(() =>
{
- // Consider tracking/clearing everything?
- Say(Responses.AreaHasMoreThanOneItem?.Format(item.Name, area.Name, item.Metadata.NameWithArticle));
- }
+ undoClear.Action();
+ undoTrack.Action();
+ ItemService.ResetProgression();
+ });
+ }
+ }
- IsDirty = true;
+ ///
+ /// 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 void TrackItemAmount(Item item, int count, float confidence)
+ {
+ ItemService.ResetProgression();
- 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();
- });
- }
+ var newItemCount = count;
+ if (item.Metadata.CounterMultiplier > 1
+ && count % item.Metadata.CounterMultiplier == 0)
+ {
+ newItemCount = count / item.Metadata.CounterMultiplier.Value;
}
- ///
- /// 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 void TrackItemAmount(Item item, int count, float confidence)
+ var oldItemCount = item.State.TrackingState;
+ if (newItemCount == oldItemCount)
{
- ItemService.ResetProgression();
+ Say(Responses.TrackedExactAmountDuplicate.Format(item.Metadata.Plural, count));
+ return;
+ }
- var newItemCount = count;
- if (item.Metadata.CounterMultiplier > 1
- && count % item.Metadata.CounterMultiplier == 0)
- {
- newItemCount = count / item.Metadata.CounterMultiplier.Value;
- }
+ item.State.TrackingState = newItemCount;
+ if (item.TryGetTrackingResponse(out var response))
+ {
+ Say(response.Format(item.Counter));
+ }
+ else if (newItemCount > oldItemCount)
+ {
+ Say(Responses.TrackedItemMultiple.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Counter, item.Name));
+ }
+ else
+ {
+ Say(Responses.UntrackedItemMultiple.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Metadata.Plural ?? $"{item.Name}s"));
+ }
- var oldItemCount = item.State.TrackingState;
- if (newItemCount == oldItemCount)
- {
- Say(Responses.TrackedExactAmountDuplicate.Format(item.Metadata.Plural, count));
- return;
- }
+ IsDirty = true;
- item.State.TrackingState = newItemCount;
- if (item.TryGetTrackingResponse(out var response))
- {
- Say(response.Format(item.Counter));
- }
- else if (newItemCount > oldItemCount)
- {
- Say(Responses.TrackedItemMultiple.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Counter, item.Name));
- }
- else
- {
- Say(Responses.UntrackedItemMultiple.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Metadata.Plural ?? $"{item.Name}s"));
- }
+ AddUndo(() => { item.State.TrackingState = oldItemCount; ItemService.ResetProgression(); });
+ OnItemTracked(new(item, null, confidence, false));
+ }
- IsDirty = true;
-
- AddUndo(() => { item.State.TrackingState = oldItemCount; ItemService.ResetProgression(); });
- 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 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();
+ ///
+ /// 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)
+ {
+ var locations = area.Locations
+ .Where(x => x.State.Cleared == false)
+ .WhereUnless(includeUnavailable, x => x.IsAvailable(ItemService.GetProgression(area)))
+ .ToImmutableList();
- ItemService.ResetProgression();
+ ItemService.ResetProgression();
- if (locations.Count == 0)
- {
- var outOfLogicLocations = area.Locations
- .Count(x => x.State.Cleared == false);
+ if (locations.Count == 0)
+ {
+ var outOfLogicLocations = area.Locations
+ .Count(x => x.State.Cleared == false);
- if (outOfLogicLocations > 1)
- Say(Responses.TrackedNothingOutOfLogic[2].Format(area.Name, outOfLogicLocations));
- else if (outOfLogicLocations > 0)
- Say(Responses.TrackedNothingOutOfLogic[1].Format(area.Name, outOfLogicLocations));
+ if (outOfLogicLocations > 1)
+ Say(Responses.TrackedNothingOutOfLogic[2].Format(area.Name, outOfLogicLocations));
+ else if (outOfLogicLocations > 0)
+ Say(Responses.TrackedNothingOutOfLogic[1].Format(area.Name, outOfLogicLocations));
+ else
+ Say(Responses.TrackedNothing.Format(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
- Say(Responses.TrackedNothing.Format(area.Name));
+ {
+ var item = onlyLocation.Item;
+ TrackItem(item: item, trackedAs: null, confidence: confidence, tryClear: true, autoTracked: false, location: onlyLocation);
+ }
}
else
{
- // If there is only one (available) item here, just call the
- // regular TrackItem instead
- var onlyLocation = locations.TrySingle();
- if (onlyLocation != null)
+ // Otherwise, start counting
+ var itemsCleared = 0;
+ var itemsTracked = new List
- ();
+ var treasureTracked = 0;
+ foreach (var location in locations)
{
+ itemsCleared++;
if (!trackItems)
{
- Clear(onlyLocation, confidence);
+ 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
- {
- var item = onlyLocation.Item;
- TrackItem(item: item, trackedAs: null, confidence: confidence, tryClear: true, autoTracked: false, location: onlyLocation);
- }
+ itemsTracked.Add(item);
+ if (IsTreasure(location.Item) || World.Config.ZeldaKeysanity)
+ treasureTracked++;
+
+ location.State.Cleared = true;
}
- else
+
+ if (trackItems)
{
- // 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 itemNames = confidence >= Options.MinimumSassConfidence
+ ? NaturalLanguage.Join(itemsTracked, World.Config)
+ : $"{itemsCleared} items";
+ Say(x => x.TrackedMultipleItems, itemsCleared, area.Name, itemNames);
- 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++;
+ var roomInfo = area is Room room ? room.Metadata : null;
+ var regionInfo = area is Region region ? region.Metadata : null;
- location.State.Cleared = true;
+ if (roomInfo?.OutOfLogic != null)
+ {
+ Say(roomInfo.OutOfLogic);
}
-
- if (trackItems)
+ else if (regionInfo?.OutOfLogic != null)
{
- var itemNames = confidence >= Options.MinimumSassConfidence
- ? NaturalLanguage.Join(itemsTracked, World.Config)
- : $"{itemsCleared} items";
- Say(x => x.TrackedMultipleItems, 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(roomInfo.OutOfLogic);
- }
- else if (regionInfo?.OutOfLogic != null)
- {
- Say(regionInfo.OutOfLogic);
- }
- else
+ Say(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 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, someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name, missingItemNames);
+ }
+ else
{
- 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, someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name, missingItemNames);
- }
- else
- {
- Say(x => x.TrackedOutOfLogicItemTooManyMissing, someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name);
- }
+ Say(x => x.TrackedOutOfLogicItemTooManyMissing, someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name);
}
}
}
- else
- {
- Say(x => x.ClearedMultipleItems, itemsCleared, area.Name);
- }
+ }
+ else
+ {
+ Say(x => x.ClearedMultipleItems, itemsCleared, area.Name);
+ }
- if (treasureTracked > 0)
+ if (treasureTracked > 0)
+ {
+ var dungeon = GetDungeonFromArea(area);
+ if (dungeon != null)
{
- var dungeon = GetDungeonFromArea(area);
- if (dungeon != null)
- {
- TrackDungeonTreasure(dungeon, amount: treasureTracked);
- }
+ TrackDungeonTreasure(dungeon, amount: treasureTracked);
}
}
-
- OnItemTracked(new ItemTrackedEventArgs(null, null, confidence, false));
}
- IsDirty = true;
+ OnItemTracked(new ItemTrackedEventArgs(null, null, confidence, false));
+ }
+
+ IsDirty = true;
- AddUndo(() =>
+ AddUndo(() =>
+ {
+ foreach (var location in locations)
{
- foreach (var location in locations)
+ if (trackItems)
{
- if (trackItems)
- {
- var item = location.Item;
- if (item.Type != ItemType.Nothing && item.State.TrackingState > 0)
- item.State.TrackingState--;
- }
-
- location.State.Cleared = false;
+ var item = location.Item;
+ if (item.Type != ItemType.Nothing && item.State.TrackingState > 0)
+ item.State.TrackingState--;
}
- ItemService.ResetProgression();
- });
+
+ 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 void ClearDungeon(IDungeon dungeon, float? confidence = null)
+ {
+ var remaining = dungeon.DungeonState.RemainingTreasure;
+ if (remaining > 0)
+ {
+ dungeon.DungeonState.RemainingTreasure = 0;
}
- ///
- /// Marks all locations and treasure within a dungeon as cleared.
- ///
- /// The dungeon to clear.
- /// The speech recognition confidence.
- public void ClearDungeon(IDungeon dungeon, float? confidence = null)
+ // 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)
{
- var remaining = dungeon.DungeonState.RemainingTreasure;
- if (remaining > 0)
+ foreach (var state in locations.Select(x => x.State).NonNull())
{
- dungeon.DungeonState.RemainingTreasure = 0;
+ state.Cleared = true;
}
+ }
- // Clear the dungeon only if there's no bosses to defeat
- if (!dungeon.DungeonState.HasReward)
- dungeon.DungeonState.Cleared = true;
+ if (remaining <= 0 && locations.Count <= 0)
+ {
+ // We didn't do anything
+ Say(x => x.DungeonAlreadyCleared, dungeon.DungeonMetadata.Name);
+ return;
+ }
- 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)
+ Say(x => x.DungeonCleared, 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 _);
+ if (missingItemCombinations.Any())
{
- foreach (var state in locations.Select(x => x.State).NonNull())
- {
- state.Cleared = true;
- }
+ var missingItems = (missingItemCombinations.Random(s_random) ?? missingItemCombinations.First())
+ .Select(ItemService.FirstOrDefault)
+ .NonNull();
+ var missingItemsText = NaturalLanguage.Join(missingItems, World.Config);
+ Say(x => x.DungeonClearedWithInaccessibleItems, dungeon.DungeonMetadata.Name, locationInfo.Name, missingItemsText);
}
-
- if (remaining <= 0 && locations.Count <= 0)
+ else
{
- // We didn't do anything
- Say(x => x.DungeonAlreadyCleared, dungeon.DungeonMetadata.Name);
- return;
+ Say(x => x.DungeonClearedWithTooManyInaccessibleItems, dungeon.DungeonMetadata.Name, locationInfo.Name);
}
+ }
+ ItemService.ResetProgression();
- Say(x => x.DungeonCleared, dungeon.DungeonMetadata.Name);
- if (inaccessibleLocations.Count > 0 && confidence >= Options.MinimumSassConfidence)
+ 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())
{
- var anyMissedLocation = inaccessibleLocations.Random(s_random) ?? inaccessibleLocations.First();
- var locationInfo = anyMissedLocation.Metadata;
- var missingItemCombinations = Logic.GetMissingRequiredItems(anyMissedLocation, progress, out _);
- 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, dungeon.DungeonMetadata.Name, locationInfo.Name, missingItemsText);
- }
- else
- {
- Say(x => x.DungeonClearedWithTooManyInaccessibleItems, dungeon.DungeonMetadata.Name, locationInfo.Name);
- }
+ state.Cleared = false;
}
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();
- });
- }
+ ///
+ /// Clears an item from the specified location.
+ ///
+ /// The location to clear.
+ /// The speech recognition confidence.
+ /// If this was tracked by the auto tracker
+ public void Clear(Location location, float? confidence = null, bool autoTracked = false)
+ {
+ ItemService.ResetProgression();
+ location.State.Cleared = true;
- ///
- /// Clears an item from the specified location.
- ///
- /// The location to clear.
- /// The speech recognition confidence.
- /// If this was tracked by the auto tracker
- public void Clear(Location location, float? confidence = null, bool autoTracked = false)
+ if (confidence != null)
{
- ItemService.ResetProgression();
- location.State.Cleared = true;
+ // Only use TTS if called from a voice command
+ var locationName = location.Metadata.Name;
+ Say(Responses.LocationCleared.Format(locationName));
+ }
- if (confidence != null)
- {
- // Only use TTS if called from a voice command
- var locationName = location.Metadata.Name;
- Say(Responses.LocationCleared.Format(locationName));
- }
+ if (location.State.HasMarkedItem)
+ {
+ location.State.MarkedItem = null;
+ OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
+ }
- if (location.State.HasMarkedItem)
- {
- location.State.MarkedItem = null;
- OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
- }
+ var undoTrackTreasure = TryTrackDungeonTreasure(location, confidence);
- var undoTrackTreasure = TryTrackDungeonTreasure(location, confidence);
+ Action? undoStopPegWorldMode = null;
+ if (location == World.DarkWorldNorthWest.PegWorld)
+ {
+ StopPegWorldMode();
- Action? undoStopPegWorldMode = null;
- if (location == World.DarkWorldNorthWest.PegWorld)
+ if (!autoTracked)
{
- StopPegWorldMode();
-
- if (!autoTracked)
- {
- undoStopPegWorldMode = _undoHistory.Pop().Action;
- }
+ undoStopPegWorldMode = _undoHistory.Pop().Action;
}
+ }
- IsDirty = true;
+ IsDirty = true;
- if (!autoTracked)
+ if (!autoTracked)
+ {
+ AddUndo(() =>
{
- AddUndo(() =>
- {
- location.State.Cleared = false;
- undoTrackTreasure?.Invoke();
- undoStopPegWorldMode?.Invoke();
- ItemService.ResetProgression();
- });
- }
-
- World.LastClearedLocation = location;
- OnLocationCleared(new(location, confidence, autoTracked));
+ location.State.Cleared = false;
+ undoTrackTreasure?.Invoke();
+ undoStopPegWorldMode?.Invoke();
+ ItemService.ResetProgression();
+ });
}
- ///
- /// 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 void MarkDungeonAsCleared(IDungeon dungeon, float? confidence = null, bool autoTracked = false)
+ World.LastClearedLocation = location;
+ OnLocationCleared(new(location, confidence, autoTracked));
+ }
+
+ ///
+ /// 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 void MarkDungeonAsCleared(IDungeon dungeon, float? confidence = null, bool autoTracked = false)
+ {
+ if (dungeon.DungeonState.Cleared)
{
- if (dungeon.DungeonState.Cleared)
- {
- if (!autoTracked)
- Say(Responses.DungeonBossAlreadyCleared.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss));
- else
- OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
+ if (!autoTracked)
+ Say(Responses.DungeonBossAlreadyCleared.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss));
+ else
+ OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
- return;
- }
+ return;
+ }
- ItemService.ResetProgression();
+ ItemService.ResetProgression();
- var addedEvent = History.AddEvent(
- HistoryEventType.BeatBoss,
- true,
- dungeon.DungeonMetadata.Boss.ToString() ?? $"boss of {dungeon.DungeonMetadata.Name}"
- );
+ 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)
+ // 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))
{
- foreach (var location in ((Region)dungeon).Locations.Where(x => !x.State.Cleared))
+ location.State.Cleared = true;
+ if (autoTracked)
{
- location.State.Cleared = true;
- if (autoTracked)
- {
- location.State.Autotracked = true;
- }
- clearedLocations.Add(location);
+ 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);
- }
+ // 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(Responses.DungeonBossCleared.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss));
- IsDirty = true;
- RestartIdleTimers();
- OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
+ dungeon.DungeonState.Cleared = true;
+ Say(Responses.DungeonBossCleared.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss));
+ IsDirty = true;
+ RestartIdleTimers();
+ OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked));
- if (!autoTracked)
+ if (!autoTracked)
+ {
+ AddUndo(() =>
{
- AddUndo(() =>
+ ItemService.ResetProgression();
+ dungeon.DungeonState.Cleared = false;
+ addedEvent.IsUndone = true;
+ foreach (var location in clearedLocations)
{
- ItemService.ResetProgression();
- dungeon.DungeonState.Cleared = false;
- addedEvent.IsUndone = true;
- foreach (var location in clearedLocations)
- {
- location.State.Cleared = false;
- }
- });
- }
+ location.State.Cleared = 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)
+ ///
+ /// 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)
+ {
+ if (boss.State.Defeated)
{
- if (boss.State.Defeated)
- {
- if (!autoTracked)
- Say(x => x.BossAlreadyDefeated, boss.Name);
- else
- OnBossUpdated(new(boss, confidence, autoTracked));
- return;
- }
+ if (!autoTracked)
+ Say(x => x.BossAlreadyDefeated, boss.Name);
+ else
+ OnBossUpdated(new(boss, confidence, autoTracked));
+ return;
+ }
- boss.State.Defeated = true;
+ boss.State.Defeated = true;
- if (!admittedGuilt && boss.Metadata.WhenTracked != null)
- Say(boss.Metadata.WhenTracked, boss.Name);
- else
- Say(boss.Metadata.WhenDefeated ?? Responses.BossDefeated, boss.Name);
+ if (!admittedGuilt && boss.Metadata.WhenTracked != null)
+ Say(boss.Metadata.WhenTracked, boss.Name);
+ else
+ Say(boss.Metadata.WhenDefeated ?? Responses.BossDefeated, boss.Name);
- var addedEvent = History.AddEvent(
- HistoryEventType.BeatBoss,
- true,
- boss.Name
- );
+ var addedEvent = History.AddEvent(
+ HistoryEventType.BeatBoss,
+ true,
+ boss.Name
+ );
- IsDirty = true;
- ItemService.ResetProgression();
+ IsDirty = true;
+ ItemService.ResetProgression();
- RestartIdleTimers();
- OnBossUpdated(new(boss, confidence, autoTracked));
+ RestartIdleTimers();
+ OnBossUpdated(new(boss, confidence, autoTracked));
- if (!autoTracked)
+ if (!autoTracked)
+ {
+ AddUndo(() =>
{
- AddUndo(() =>
- {
- boss.State.Defeated = false;
- addedEvent.IsUndone = true;
- });
- }
+ boss.State.Defeated = false;
+ addedEvent.IsUndone = true;
+ });
}
+ }
- ///
- /// 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 boss as defeated.
+ ///
+ /// The boss that should be 'revived'.
+ /// The speech recognition confidence.
+ public void MarkBossAsNotDefeated(Boss boss, float? confidence = null)
+ {
+ if (boss.State.Defeated != true)
{
- if (boss.State.Defeated != true)
- {
- Say(x => x.BossNotYetDefeated, boss.Name);
- return;
- }
+ Say(x => x.BossNotYetDefeated, boss.Name);
+ return;
+ }
- boss.State.Defeated = false;
- Say(Responses.BossUndefeated, boss.Name);
+ boss.State.Defeated = false;
+ Say(Responses.BossUndefeated, boss.Name);
- IsDirty = true;
- ItemService.ResetProgression();
+ IsDirty = true;
+ ItemService.ResetProgression();
- OnBossUpdated(new(boss, confidence, false));
- AddUndo(() => boss.State.Defeated = true);
- }
+ OnBossUpdated(new(boss, confidence, false));
+ AddUndo(() => boss.State.Defeated = true);
+ }
- ///
- /// 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 MarkDungeonAsIncomplete(IDungeon dungeon, 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 MarkDungeonAsIncomplete(IDungeon dungeon, float? confidence = null)
+ {
+ if (!dungeon.DungeonState.Cleared)
{
- if (!dungeon.DungeonState.Cleared)
- {
- Say(Responses.DungeonBossNotYetCleared.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss));
- return;
- }
+ Say(Responses.DungeonBossNotYetCleared.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss));
+ return;
+ }
- ItemService.ResetProgression();
- dungeon.DungeonState.Cleared = false;
- Say(Responses.DungeonBossUncleared.Format(dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss));
+ ItemService.ResetProgression();
+ dungeon.DungeonState.Cleared = false;
+ Say(Responses.DungeonBossUncleared.Format(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.DungeonMetadata.LocationId != null)
+ // Try to untrack the associated boss reward item
+ Action? undoUnclear = null;
+ Action? undoUntrackTreasure = null;
+ Action? undoUntrack = null;
+ if (dungeon.DungeonMetadata.LocationId != null)
+ {
+ var rewardLocation = _worldService.Location(dungeon.DungeonMetadata.LocationId.Value);
+ if (rewardLocation.Item.Type != ItemType.Nothing)
{
- var rewardLocation = _worldService.Location(dungeon.DungeonMetadata.LocationId.Value);
- if (rewardLocation.Item.Type != ItemType.Nothing)
+ var item = rewardLocation.Item;
+ if (item.Type != ItemType.Nothing && item.State.TrackingState > 0)
{
- 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--;
- }
+ UntrackItem(item);
+ undoUntrack = _undoHistory.Pop().Action;
}
- if (rewardLocation.State.Cleared)
+ if (!rewardLocation.Item.IsDungeonItem)
{
- rewardLocation.State.Cleared = false;
- OnLocationCleared(new(rewardLocation, null, false));
- undoUnclear = () => rewardLocation.State.Cleared = true;
+ dungeon.DungeonState.RemainingTreasure++;
+ undoUntrackTreasure = () => dungeon.DungeonState.RemainingTreasure--;
}
}
- IsDirty = true;
-
- OnDungeonUpdated(new(dungeon, confidence, false));
- AddUndo(() =>
+ if (rewardLocation.State.Cleared)
{
- dungeon.DungeonState.Cleared = false;
- undoUntrack?.Invoke();
- undoUntrackTreasure?.Invoke();
- undoUnclear?.Invoke();
- ItemService.ResetProgression();
- });
+ rewardLocation.State.Cleared = false;
+ OnLocationCleared(new(rewardLocation, null, false));
+ undoUnclear = () => rewardLocation.State.Cleared = true;
+ }
}
- ///
- /// Marks an item at the specified location.
- ///
- /// The location to mark.
- ///
- /// The item that is found at .
- ///
- /// The speech recognition confidence.
- public void MarkLocation(Location location, Item item, float? confidence = null)
- {
- var locationName = location.Metadata.Name;
- GiveLocationComment(item, location, isTracking: false, confidence);
+ IsDirty = true;
- if (item.Type == ItemType.Nothing)
- {
- Clear(location);
- Say(Responses.LocationMarkedAsBullshit.Format(locationName));
- }
- else if (location.State.MarkedItem != null)
- {
- var oldType = location.State.MarkedItem;
- location.State.MarkedItem = item.Type;
- Say(Responses.LocationMarkedAgain.Format(locationName, item.Name, oldType.GetDescription()));
- AddUndo(() => location.State.MarkedItem = oldType);
- }
- else
- {
- location.State.MarkedItem = item.Type;
- Say(Responses.LocationMarked.Format(locationName, item.Name));
- AddUndo(() => location.State.MarkedItem = null);
- }
+ OnDungeonUpdated(new(dungeon, confidence, false));
+ AddUndo(() =>
+ {
+ dungeon.DungeonState.Cleared = false;
+ undoUntrack?.Invoke();
+ undoUntrackTreasure?.Invoke();
+ undoUnclear?.Invoke();
+ ItemService.ResetProgression();
+ });
+ }
- IsDirty = true;
+ ///
+ /// Marks an item at the specified location.
+ ///
+ /// The location to mark.
+ ///
+ /// The item that is found at .
+ ///
+ /// The speech recognition confidence.
+ public void MarkLocation(Location location, Item item, float? confidence = null)
+ {
+ var locationName = location.Metadata.Name;
+ GiveLocationComment(item, location, isTracking: false, confidence);
- OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
+ if (item.Type == ItemType.Nothing)
+ {
+ Clear(location);
+ Say(Responses.LocationMarkedAsBullshit.Format(locationName));
}
-
- ///
- /// Pegs a Peg World peg.
- ///
- /// The speech recognition confidence.
- public void Peg(float? confidence = null)
+ else if (location.State.MarkedItem != null)
{
- if (!PegWorldMode)
- return;
+ var oldType = location.State.MarkedItem;
+ location.State.MarkedItem = item.Type;
+ Say(Responses.LocationMarkedAgain.Format(locationName, item.Name, oldType.GetDescription()));
+ AddUndo(() => location.State.MarkedItem = oldType);
+ }
+ else
+ {
+ location.State.MarkedItem = item.Type;
+ Say(Responses.LocationMarked.Format(locationName, item.Name));
+ AddUndo(() => location.State.MarkedItem = null);
+ }
- PegsPegged++;
+ IsDirty = true;
- if (PegsPegged < PegWorldModeModule.TotalPegs)
- Say(Responses.PegWorldModePegged);
- else
- Say(Responses.PegWorldModeDone);
- OnPegPegged(new TrackerEventArgs(confidence));
- AddUndo(() => PegsPegged--);
+ OnMarkedLocationsUpdated(new TrackerEventArgs(confidence));
+ }
- RestartIdleTimers();
- }
+ ///
+ /// Pegs a Peg World peg.
+ ///
+ /// The speech recognition confidence.
+ public void Peg(float? confidence = null)
+ {
+ if (!PegWorldMode)
+ return;
- ///
- /// Starts Peg World mode.
- ///
- /// The speech recognition confidence.
- public void StartPegWorldMode(float? confidence = null)
- {
- ShutUp();
- PegWorldMode = true;
- Say(Responses.PegWorldModeOn, wait: true);
- OnPegWorldModeToggled(new TrackerEventArgs(confidence));
- AddUndo(() => PegWorldMode = false);
- }
+ PegsPegged++;
- ///
- /// Turns Peg World mode off.
- ///
- /// The speech recognition confidence.
- public void StopPegWorldMode(float? confidence = null)
- {
- PegWorldMode = false;
+ if (PegsPegged < PegWorldModeModule.TotalPegs)
+ Say(Responses.PegWorldModePegged);
+ else
Say(Responses.PegWorldModeDone);
- OnPegWorldModeToggled(new TrackerEventArgs(confidence));
- AddUndo(() => PegWorldMode = true);
- }
+ OnPegPegged(new TrackerEventArgs(confidence));
+ AddUndo(() => PegsPegged--);
- ///
- /// Starts Peg World mode.
- ///
- /// The speech recognition confidence.
- public void StartShaktoolMode(float? confidence = null)
- {
- ShaktoolMode = true;
- OnShaktoolModeToggled(new TrackerEventArgs(confidence));
- AddUndo(() => ShaktoolMode = false);
- }
+ RestartIdleTimers();
+ }
- ///
- /// Turns Peg World mode off.
- ///
- /// The speech recognition confidence.
- public void StopShaktoolMode(float? confidence = null)
- {
- ShaktoolMode = false;
- OnShaktoolModeToggled(new TrackerEventArgs(confidence));
- AddUndo(() => ShaktoolMode = true);
- }
+ ///
+ /// Starts Peg World mode.
+ ///
+ /// The speech recognition confidence.
+ public void StartPegWorldMode(float? confidence = null)
+ {
+ ShutUp();
+ PegWorldMode = true;
+ Say(Responses.PegWorldModeOn, wait: true);
+ OnPegWorldModeToggled(new TrackerEventArgs(confidence));
+ AddUndo(() => PegWorldMode = false);
+ }
- ///
- /// 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)
- {
- UpdateRegion(region.Metadata, updateMap, resetTime);
- }
+ ///
+ /// Turns Peg World mode off.
+ ///
+ /// The speech recognition confidence.
+ public void StopPegWorldMode(float? confidence = null)
+ {
+ PegWorldMode = false;
+ Say(Responses.PegWorldModeDone);
+ OnPegWorldModeToggled(new TrackerEventArgs(confidence));
+ AddUndo(() => PegWorldMode = true);
+ }
- ///
- /// 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(RegionInfo? region, bool updateMap = false, bool resetTime = false)
- {
- if (region != CurrentRegion)
- {
- if (resetTime && History.GetHistory().Count == 0)
- {
- ResetTimer(true);
- }
+ ///
+ /// Starts Peg World mode.
+ ///
+ /// The speech recognition confidence.
+ public void StartShaktoolMode(float? confidence = null)
+ {
+ ShaktoolMode = true;
+ OnShaktoolModeToggled(new TrackerEventArgs(confidence));
+ AddUndo(() => ShaktoolMode = false);
+ }
- History.AddEvent(
- HistoryEventType.EnteredRegion,
- true,
- region?.Name.ToString() ?? "new region"
- );
- }
+ ///
+ /// Turns Peg World mode off.
+ ///
+ /// The speech recognition confidence.
+ public void StopShaktoolMode(float? confidence = null)
+ {
+ ShaktoolMode = false;
+ OnShaktoolModeToggled(new TrackerEventArgs(confidence));
+ AddUndo(() => ShaktoolMode = true);
+ }
+
+ ///
+ /// 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)
+ {
+ UpdateRegion(region.Metadata, updateMap, resetTime);
+ }
- CurrentRegion = region;
- if (updateMap && region != 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 void UpdateRegion(RegionInfo? region, bool updateMap = false, bool resetTime = false)
+ {
+ if (region != CurrentRegion)
+ {
+ if (resetTime && History.GetHistory().Count == 0)
{
- UpdateMap(region.MapName);
+ ResetTimer(true);
}
+
+ History.AddEvent(
+ HistoryEventType.EnteredRegion,
+ true,
+ region?.Name.ToString() ?? "new region"
+ );
}
- ///
- /// Updates the map to display for the user
- ///
- /// The name of the map
- public void UpdateMap(string map)
+ CurrentRegion = region;
+ if (updateMap && region != null)
{
- CurrentMap = map;
- MapUpdated?.Invoke(this, EventArgs.Empty);
+ 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)
+ ///
+ /// 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 (!_beatenGame)
{
- if (!_beatenGame)
+ _beatenGame = true;
+ var pauseUndo = PauseTimer(false);
+ Say(x => x.BeatGame);
+ BeatGame?.Invoke(this, new TrackerEventArgs(autoTracked));
+ if (!autoTracked)
{
- _beatenGame = true;
- var pauseUndo = PauseTimer(false);
- Say(x => x.BeatGame);
- BeatGame?.Invoke(this, new TrackerEventArgs(autoTracked));
- if (!autoTracked)
+ AddUndo(() =>
{
- AddUndo(() =>
+ _beatenGame = false;
+ if (pauseUndo != null)
{
- _beatenGame = false;
- if (pauseUndo != null)
- {
- pauseUndo();
- }
- });
- }
+ pauseUndo();
+ }
+ });
}
}
+ }
- ///
- /// Called when the player has died
- ///
- public void TrackDeath(bool autoTracked)
- {
- PlayerDied?.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 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));
- }
+ ///
+ /// 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));
+ }
- internal void RestartIdleTimers()
+ public void RestartIdleTimers()
+ {
+ foreach (var item in _idleTimers)
{
- foreach (var item in _idleTimers)
- {
- var timeout = Parse.AsTimeSpan(item.Key, s_random) ?? Timeout.InfiniteTimeSpan;
- var timer = item.Value;
+ var timeout = Parse.AsTimeSpan(item.Key, s_random) ?? Timeout.InfiniteTimeSpan;
+ var timer = item.Value;
- timer.Change(timeout, Timeout.InfiniteTimeSpan);
- }
+ 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, .
- ///
- protected internal bool IsWorth(RewardType reward)
+ ///
+ /// Determines whether or not the specified reward is worth getting.
+ ///
+ /// The dungeon reward.
+ ///
+ /// if the reward leads to something good;
+ /// otherwise, .
+ ///
+ public bool IsWorth(RewardType reward)
+ {
+ var sahasrahlaItem = World.FindLocation(LocationId.Sahasrahla).Item;
+ if (sahasrahlaItem.Type != ItemType.Nothing && reward == RewardType.PendantGreen)
{
- 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}...", 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);
+ _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))
+ 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}...", 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);
+ _logger.LogDebug("{Reward} leads to {Item}, which is worth it", reward, pedItem);
+ return true;
}
-
- return false;
+ _logger.LogDebug("{Reward} leads to {Item}, which is junk", reward, pedItem);
}
- ///
- /// 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, .
- ///
- protected internal 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)
+ 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 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)
{
- 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}...", 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);
+ _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.
- ///
- protected internal virtual void AddUndo(Action undo) => _undoHistory.Push((undo, DateTime.Now));
+ 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 virtual void AddUndo(Action undo) => _undoHistory.Push((undo, DateTime.Now));
- ///
- /// Cleans up resources used by this class.
- ///
- ///
- /// true to dispose of managed resources.
- ///
- protected virtual void Dispose(bool disposing)
+ ///
+ /// Cleans up resources used by this class.
+ ///
+ ///
+ /// true to dispose of managed resources.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
{
- if (!_disposed)
+ if (disposing)
{
- if (disposing)
- {
- _recognizer.Dispose();
- (_communicator as IDisposable)?.Dispose();
-
- foreach (var timer in _idleTimers.Values)
- timer.Dispose();
- }
+ _recognizer.Dispose();
+ (_communicator as IDisposable)?.Dispose();
- _disposed = true;
+ foreach (var timer in _idleTimers.Values)
+ timer.Dispose();
}
+
+ _disposed = true;
}
+ }
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnItemTracked(ItemTrackedEventArgs e)
- => ItemTracked?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnPegWorldModeToggled(TrackerEventArgs e)
- => ToggledPegWorldModeOn?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnPegPegged(TrackerEventArgs e)
- => PegPegged?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnShaktoolModeToggled(TrackerEventArgs e)
- => ToggledShaktoolMode?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnDungeonUpdated(DungeonTrackedEventArgs e)
- => DungeonUpdated?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnBossUpdated(BossTrackedEventArgs e)
- => BossUpdated?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnMarkedLocationsUpdated(TrackerEventArgs e)
- => MarkedLocationsUpdated?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnGoModeToggledOn(TrackerEventArgs e)
- => GoModeToggledOn?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnLocationCleared(LocationClearedEventArgs e)
- => LocationCleared?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnActionUndone(TrackerEventArgs e)
- => ActionUndone?.Invoke(this, e);
-
- ///
- /// Raises the event.
- ///
- protected virtual void OnStateLoaded()
- => StateLoaded?.Invoke(this, EventArgs.Empty);
-
- ///
- /// Raises the event.
- ///
- /// Event data.
- protected virtual void OnSpeechRecognized(TrackerEventArgs e)
- => SpeechRecognized?.Invoke(this, e);
-
- 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
- };
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnItemTracked(ItemTrackedEventArgs e)
+ => ItemTracked?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnPegWorldModeToggled(TrackerEventArgs e)
+ => ToggledPegWorldModeOn?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnPegPegged(TrackerEventArgs e)
+ => PegPegged?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnShaktoolModeToggled(TrackerEventArgs e)
+ => ToggledShaktoolMode?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnDungeonUpdated(DungeonTrackedEventArgs e)
+ => DungeonUpdated?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnBossUpdated(BossTrackedEventArgs e)
+ => BossUpdated?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnMarkedLocationsUpdated(TrackerEventArgs e)
+ => MarkedLocationsUpdated?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnGoModeToggledOn(TrackerEventArgs e)
+ => GoModeToggledOn?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnLocationCleared(LocationClearedEventArgs e)
+ => LocationCleared?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnActionUndone(TrackerEventArgs e)
+ => ActionUndone?.Invoke(this, e);
+
+ ///
+ /// Raises the event.
+ ///
+ protected virtual void OnStateLoaded()
+ => StateLoaded?.Invoke(this, EventArgs.Empty);
+
+ ///
+ /// Raises the event.
+ ///
+ /// Event data.
+ protected virtual void OnSpeechRecognized(TrackerEventArgs e)
+ => SpeechRecognized?.Invoke(this, e);
+
+ 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(Item item, float? confidence)
+ {
+ 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;
}
- private Action? TryTrackDungeonTreasure(Item item, float? confidence)
+ var dungeon = GetDungeonFromItem(item);
+ if (dungeon != null && (IsTreasure(item) || World.Config.ZeldaKeysanity))
{
- 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;
- }
+ if (TrackDungeonTreasure(dungeon, confidence))
+ return _undoHistory.Pop().Action;
+ }
- var dungeon = GetDungeonFromItem(item);
- if (dungeon != null && (IsTreasure(item) || World.Config.ZeldaKeysanity))
- {
- if (TrackDungeonTreasure(dungeon, confidence))
- return _undoHistory.Pop().Action;
- }
+ IsDirty = true;
- IsDirty = true;
+ return 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;
}
- private Action? TryTrackDungeonTreasure(Location location, float? confidence, bool autoTracked = false, bool stateResponse = true)
+ var dungeon = GetDungeonFromLocation(location);
+ if (dungeon != null && (IsTreasure(location.Item) || World.Config.ZeldaKeysanity))
{
- 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;
- }
+ if (TrackDungeonTreasure(dungeon, confidence, 1, autoTracked, stateResponse))
+ return _undoHistory.Pop().Action;
+ }
- 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;
- IsDirty = true;
+ return null;
+ }
- return null;
+ private void GiveLocationComment(Item item, Location location, bool isTracking, float? confidence)
+ {
+ // If the plando config specifies a specific line for this location, say it
+ if (World.Config.PlandoConfig?.TrackerLocationLines.ContainsKey(location.ToString()) == true)
+ {
+ Say(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.Type != location.Item.Type && (item.Type != ItemType.Nothing || location.Item.Metadata.IsProgression(World.Config)))
+ {
+ if (confidence == null || confidence < Options.MinimumSassConfidence)
+ return;
- private void GiveLocationComment(Item item, Location location, bool isTracking, float? confidence)
+ var actualItemName = ItemService.GetName(location.Item.Type);
+ if (HintsEnabled) actualItemName = "another item";
+
+ Say(Responses.LocationHasDifferentItem?.Format(item.Metadata.NameWithArticle, actualItemName));
+ }
+ else
{
- // If the plando config specifies a specific line for this location, say it
- if (World.Config.PlandoConfig?.TrackerLocationLines.ContainsKey(location.ToString()) == true)
+ if (item.Type == location.VanillaItem && item.Type != ItemType.Nothing)
{
- Say(World.Config.PlandoConfig?.TrackerLocationLines[location.ToString()]);
+ Say(x => x.TrackedVanillaItem);
+ return;
}
- // 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.Type != location.Item.Type && (item.Type != 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(Responses.LocationHasDifferentItem?.Format(item.Metadata.NameWithArticle, actualItemName));
- }
- else
+ var locationInfo = location.Metadata;
+ var isJunk = item.Metadata.IsJunk(World.Config);
+ if (isJunk)
{
- if (item.Type == location.VanillaItem && item.Type != ItemType.Nothing)
- {
- Say(x => x.TrackedVanillaItem);
- return;
- }
-
- var locationInfo = location.Metadata;
- var isJunk = item.Metadata.IsJunk(World.Config);
- if (isJunk)
+ if (!isTracking && locationInfo.WhenMarkingJunk?.Count > 0)
{
- if (!isTracking && locationInfo.WhenMarkingJunk?.Count > 0)
- {
- Say(locationInfo.WhenMarkingJunk.Random(s_random)!);
- }
- else if (locationInfo.WhenTrackingJunk?.Count > 0)
- {
- Say(locationInfo.WhenTrackingJunk.Random(s_random)!);
- }
+ Say(locationInfo.WhenMarkingJunk.Random(s_random)!);
}
- else if (!isJunk)
+ else if (locationInfo.WhenTrackingJunk?.Count > 0)
{
- if (!isTracking && locationInfo.WhenMarkingProgression?.Count > 0)
- {
- Say(locationInfo.WhenMarkingProgression.Random(s_random)!);
- }
- else if (locationInfo.WhenTrackingProgression?.Count > 0)
- {
- Say(locationInfo.WhenTrackingProgression.Random(s_random)!);
- }
+ Say(locationInfo.WhenTrackingJunk.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)
+ else if (!isJunk)
{
- // 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))
+ if (!isTracking && locationInfo.WhenMarkingProgression?.Count > 0)
+ {
+ Say(locationInfo.WhenMarkingProgression.Random(s_random)!);
+ }
+ else if (locationInfo.WhenTrackingProgression?.Count > 0)
{
- // Be a smart-ass about it
- Say(Responses.ItemTrackedInIncorrectDungeon?.Format(dungeon.DungeonMetadata.Name, item.Metadata.NameWithArticle));
+ Say(locationInfo.WhenTrackingProgression.Random(s_random)!);
}
}
-
- // - 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 IDungeon? GetDungeonFromItem(Item item, IDungeon? dungeon = null)
+ {
+ var locations = _worldService.Locations(itemFilter: item.Type)
+ .Where(x => x.Type != LocationType.NotInDungeon)
+ .ToImmutableList();
- private void IdleTimerElapsed(object? state)
+ if (locations.Count == 1 && dungeon == null)
{
- var key = (string)state!;
- Say(Responses.Idle[key]);
+ // User didn't have a guess and there's only one location that
+ // has the tracker item
+ return GetDungeonFromLocation(locations[0]);
}
- private void Recognizer_SpeechRecognized(object? sender, SpeechRecognizedEventArgs e)
+ if (locations.Count > 0 && dungeon != null)
{
- RestartIdleTimers();
- OnSpeechRecognized(new(e.Result.Confidence, e.Result.Text));
+ // Does the dungeon even have that item?
+ if (locations.All(x => dungeon != x.Region))
+ {
+ // Be a smart-ass about it
+ Say(Responses.ItemTrackedInIncorrectDungeon?.Format(dungeon.DungeonMetadata.Name, item.Metadata.NameWithArticle));
+ }
}
- private void GiveLocationHint(IEnumerable accessibleBefore)
+ // - 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(Responses.Idle[key]);
+ }
+
+ private void Recognizer_SpeechRecognized(object? sender, SpeechRecognizedEventArgs e)
+ {
+ RestartIdleTimers();
+ OnSpeechRecognized(new(e.Result.Confidence, e.Result.Text));
+ }
+
+ private void GiveLocationHint(IEnumerable accessibleBefore)
+ {
+ var accessibleAfter = _worldService.AccessibleLocations(false);
+ var newlyAccessible = accessibleAfter.Except(accessibleBefore);
+ if (newlyAccessible.Any())
{
- var accessibleAfter = _worldService.AccessibleLocations(false);
- var newlyAccessible = accessibleAfter.Except(accessibleBefore);
- if (newlyAccessible.Any())
- {
- var regions = newlyAccessible.GroupBy(x => x.Region)
- .OrderByDescending(x => x.Count())
- .ThenBy(x => x.Key.Name);
+ var regions = newlyAccessible.GroupBy(x => x.Region)
+ .OrderByDescending(x => x.Count())
+ .ThenBy(x => x.Key.Name);
- if (newlyAccessible.Contains(World.FindLocation(LocationId.InnerMaridiaSpringBall)))
- Say(Responses.ShaktoolAvailable);
+ if (newlyAccessible.Contains(World.FindLocation(LocationId.InnerMaridiaSpringBall)))
+ Say(Responses.ShaktoolAvailable);
- if (newlyAccessible.Contains(World.DarkWorldNorthWest.PegWorld))
- Say(Responses.PegWorldAvailable);
- }
- else if (Responses.TrackedUselessItem != null)
- {
- Say(Responses.TrackedUselessItem);
- }
+ if (newlyAccessible.Contains(World.DarkWorldNorthWest.PegWorld))
+ Say(Responses.PegWorldAvailable);
+ }
+ else if (Responses.TrackedUselessItem != null)
+ {
+ Say(Responses.TrackedUselessItem);
}
-
}
+
}
diff --git a/src/Randomizer.SMZ3.Tracking/TrackerEventArgs.cs b/src/Randomizer.SMZ3.Tracking/TrackerEventArgs.cs
deleted file mode 100644
index ba4a09996..000000000
--- a/src/Randomizer.SMZ3.Tracking/TrackerEventArgs.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-
-namespace Randomizer.SMZ3.Tracking
-{
- ///
- /// Contains event data for tracking events.
- ///
- public class TrackerEventArgs : EventArgs
- {
- ///
- /// Initializes a new instance of the
- /// class.
- ///
- /// The speech recognition confidence.
- /// If the event was triggered by auto tracker
- public TrackerEventArgs(float? confidence, bool autoTracked = false)
- {
- Confidence = confidence;
- AutoTracked = autoTracked;
- }
-
- ///
- /// Initializes a new instance of the
- /// class.
- ///
- /// The speech recognition confidence.
- /// The phrase that was recognized.
- public TrackerEventArgs(float? confidence, string? phrase)
- {
- Confidence = confidence;
- Phrase = phrase;
- }
-
- ///
- /// Initializes a new instance of the
- /// class.
- ///
- /// If the event was triggered by auto tracker
- public TrackerEventArgs(bool autoTracked)
- {
- AutoTracked = autoTracked;
- }
-
- ///
- /// Gets the speech recognition confidence as a value between 0.0 and
- /// 1.0, or null if the event was not initiated by speech
- /// recognition.
- ///
- public float? Confidence { get; }
-
- ///
- /// Gets the phrase Tracker recognized, or null.
- ///
- public string? Phrase { get; }
-
- ///
- /// If the event was triggered by auto tracker
- ///
- public bool AutoTracked { get; init; }
- }
-}
diff --git a/src/Randomizer.SMZ3.Tracking/TrackerServiceCollectionExtensions.cs b/src/Randomizer.SMZ3.Tracking/TrackerServiceCollectionExtensions.cs
index 56d05e26d..96b83b1ee 100644
--- a/src/Randomizer.SMZ3.Tracking/TrackerServiceCollectionExtensions.cs
+++ b/src/Randomizer.SMZ3.Tracking/TrackerServiceCollectionExtensions.cs
@@ -4,102 +4,98 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
-
-using Randomizer.SMZ3.Contracts;
+using Randomizer.Abstractions;
using Randomizer.SMZ3.Tracking.AutoTracking.MetroidStateChecks;
using Randomizer.SMZ3.Tracking.AutoTracking.ZeldaStateChecks;
-using Randomizer.Data.Configuration;
using Randomizer.SMZ3.Tracking.Services;
using Randomizer.SMZ3.Tracking.VoiceCommands;
-using Randomizer.Data;
using Randomizer.Data.Options;
-namespace Randomizer.SMZ3.Tracking
+namespace Randomizer.SMZ3.Tracking;
+
+///
+/// Provides methods for adding tracking services to a service collection.
+///
+public static class TrackerServiceCollectionExtensions
{
///
- /// Provides methods for adding tracking services to a service collection.
+ /// Adds the services required to start using Tracker.
///
- public static class TrackerServiceCollectionExtensions
+ ///
+ /// The service collection to add Tracker to.
+ ///
+ /// A reference to .
+ public static IServiceCollection AddTracker(this IServiceCollection services)
{
- ///
- /// Adds the services required to start using Tracker.
- ///
- ///
- /// The service collection to add Tracker to.
- ///
- /// A reference to .
- public static IServiceCollection AddTracker(this IServiceCollection services)
- {
- services.AddBasicTrackerModules();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
+ services.AddBasicTrackerModules();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
- if (OperatingSystem.IsWindows())
- {
- services.AddScoped();
- }
- else
- {
- services.AddScoped();
- }
-
- var assemblies = new[] { Assembly.GetExecutingAssembly() };
+ if (OperatingSystem.IsWindows())
+ {
+ services.AddScoped();
+ }
+ else
+ {
+ services.AddScoped();
+ }
- var zeldaStateChecks = assemblies
- .SelectMany(a => a.GetTypes())
- .Where(t => !t.IsAbstract && t.IsClass && t.GetInterface(nameof(IZeldaStateCheck)) == typeof(IZeldaStateCheck));
- foreach (var stateCheck in zeldaStateChecks)
- {
- services.Add(new ServiceDescriptor(typeof(IZeldaStateCheck), stateCheck, ServiceLifetime.Transient));
- }
+ var assemblies = new[] { Assembly.GetExecutingAssembly() };
- var metroidStateChecks = assemblies
- .SelectMany(a => a.GetTypes())
- .Where(t => !t.IsAbstract && t.IsClass && t.GetInterface(nameof(IMetroidStateCheck)) == typeof(IMetroidStateCheck));
- foreach (var stateCheck in metroidStateChecks)
- {
- services.Add(new ServiceDescriptor(typeof(IMetroidStateCheck), stateCheck, ServiceLifetime.Transient));
- }
+ var zeldaStateChecks = assemblies
+ .SelectMany(a => a.GetTypes())
+ .Where(t => !t.IsAbstract && t.IsClass && t.GetInterface(nameof(IZeldaStateCheck)) == typeof(IZeldaStateCheck));
+ foreach (var stateCheck in zeldaStateChecks)
+ {
+ services.Add(new ServiceDescriptor(typeof(IZeldaStateCheck), stateCheck, ServiceLifetime.Transient));
+ }
- return services;
+ var metroidStateChecks = assemblies
+ .SelectMany(a => a.GetTypes())
+ .Where(t => !t.IsAbstract && t.IsClass && t.GetInterface(nameof(IMetroidStateCheck)) == typeof(IMetroidStateCheck));
+ foreach (var stateCheck in metroidStateChecks)
+ {
+ services.Add(new ServiceDescriptor(typeof(IMetroidStateCheck), stateCheck, ServiceLifetime.Transient));
}
+ return services;
+ }
- ///
- /// Enables the specified tracker module.
- ///
- /// The type of module to enable.
- ///
- /// The service collection to add the tracker module to.
- ///
- /// A reference to .
- public static IServiceCollection AddOptionalModule(this IServiceCollection services)
- where TModule : TrackerModule
- {
- services.TryAddEnumerable(ServiceDescriptor.Scoped());
- return services;
- }
- private static IServiceCollection AddBasicTrackerModules(this IServiceCollection services)
- {
- var moduleTypes = typeof(TAssembly).Assembly.GetTypes()
- .Where(x => x.IsSubclassOf(typeof(TrackerModule)));
+ ///
+ /// Enables the specified tracker module.
+ ///
+ /// The type of module to enable.
+ ///
+ /// The service collection to add the tracker module to.
+ ///
+ /// A reference to .
+ public static IServiceCollection AddOptionalModule(this IServiceCollection services)
+ where TModule : TrackerModule
+ {
+ services.TryAddEnumerable(ServiceDescriptor.Scoped());
+ return services;
+ }
- foreach (var moduleType in moduleTypes)
- {
- services.TryAddEnumerable(ServiceDescriptor.Scoped(typeof(TrackerModule), moduleType));
- }
+ private static IServiceCollection AddBasicTrackerModules(this IServiceCollection services)
+ {
+ var moduleTypes = typeof(TAssembly).Assembly.GetTypes()
+ .Where(x => x.IsSubclassOf(typeof(TrackerModule)));
- return services;
+ foreach (var moduleType in moduleTypes)
+ {
+ services.TryAddEnumerable(ServiceDescriptor.Scoped(typeof(TrackerModule), moduleType));
}
+
+ return services;
}
}
diff --git a/src/Randomizer.SMZ3.Tracking/VoiceCommands/AutoTrackerModule.cs b/src/Randomizer.SMZ3.Tracking/VoiceCommands/AutoTrackerModule.cs
index fb1ee2c0c..ff18ed5f5 100644
--- a/src/Randomizer.SMZ3.Tracking/VoiceCommands/AutoTrackerModule.cs
+++ b/src/Randomizer.SMZ3.Tracking/VoiceCommands/AutoTrackerModule.cs
@@ -1,66 +1,64 @@
using System;
using Microsoft.Extensions.Logging;
-using Randomizer.SMZ3.Tracking.AutoTracking;
+using Randomizer.Abstractions;
using Randomizer.SMZ3.Tracking.Services;
using Randomizer.Data.Options;
-namespace Randomizer.SMZ3.Tracking.VoiceCommands
+namespace Randomizer.SMZ3.Tracking.VoiceCommands;
+
+///
+/// Module for creating the auto tracker and interacting with the auto tracker
+///
+public class AutoTrackerModule : TrackerModule, IDisposable
{
+ private readonly IAutoTracker _autoTracker;
+
///
- /// Module for creating the auto tracker and interacting with the auto tracker
+ /// Initializes a new instance of the
+ /// class.
///
- public class AutoTrackerModule : TrackerModule, IDisposable
+ /// The tracker instance.
+ /// Service to get item information
+ /// Service to get world information
+ /// Used to write logging information.
+ /// The auto tracker to associate with this module
+ public AutoTrackerModule(ITracker tracker, IItemService itemService, IWorldService worldService, ILogger logger, IAutoTracker autoTracker)
+ : base(tracker, itemService, worldService, logger)
{
- private readonly AutoTracker _autoTracker;
-
- ///
- /// Initializes a new instance of the
- /// class.
- ///
- /// The tracker instance.
- /// Service to get item information
- /// Service to get world information
- /// Used to write logging information.
- /// The auto tracker to associate with this module
- public AutoTrackerModule(Tracker tracker, IItemService itemService, IWorldService worldService, ILogger logger, AutoTracker autoTracker)
- : base(tracker, itemService, worldService, logger)
- {
- Tracker.AutoTracker = autoTracker;
- _autoTracker = autoTracker;
- }
+ Tracker.AutoTracker = autoTracker;
+ _autoTracker = autoTracker;
+ }
- private GrammarBuilder GetLookAtGameRule()
- {
- return new GrammarBuilder()
- .Append("Hey tracker, ")
- .Optional("please", "would you please")
- .OneOf("look at this", "look here", "record this", "log this", "take a look at this", "get a load of this")
- .Optional("shit", "crap");
- }
+ private GrammarBuilder GetLookAtGameRule()
+ {
+ return new GrammarBuilder()
+ .Append("Hey tracker, ")
+ .Optional("please", "would you please")
+ .OneOf("look at this", "look here", "record this", "log this", "take a look at this", "get a load of this")
+ .Optional("shit", "crap");
+ }
- private void LookAtGame()
+ private void LookAtGame()
+ {
+ if (_autoTracker.LatestViewAction == null || _autoTracker.LatestViewAction.Invoke() == false)
{
- if (_autoTracker.LatestViewAction == null || _autoTracker.LatestViewAction.Invoke() == false)
- {
- Tracker.Say(x => x.AutoTracker.LookedAtNothing);
- }
+ Tracker.Say(x => x.AutoTracker.LookedAtNothing);
}
+ }
- ///
- /// Called when the module is destroyed
- ///
- public void Dispose()
- {
- _autoTracker.SetConnector(EmulatorConnectorType.None, "");
- }
+ ///
+ /// Called when the module is destroyed
+ ///
+ public void Dispose()
+ {
+ _autoTracker.SetConnector(EmulatorConnectorType.None, "");
+ }
- public override void AddCommands()
+ public override void AddCommands()
+ {
+ AddCommand("Look at this", GetLookAtGameRule(), (result) =>
{
- AddCommand("Look at this", GetLookAtGameRule(), (result) =>
- {
- LookAtGame();
- });
- }
+ LookAtGame();
+ });
}
-
}
diff --git a/src/Randomizer.SMZ3.Tracking/VoiceCommands/BossTrackingModule.cs b/src/Randomizer.SMZ3.Tracking/VoiceCommands/BossTrackingModule.cs
index e1a197698..5267ef249 100644
--- a/src/Randomizer.SMZ3.Tracking/VoiceCommands/BossTrackingModule.cs
+++ b/src/Randomizer.SMZ3.Tracking/VoiceCommands/BossTrackingModule.cs
@@ -1,7 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
-
+using Randomizer.Abstractions;
using Randomizer.Shared;
using Randomizer.SMZ3.Tracking.Services;
@@ -20,7 +20,7 @@ public class BossTrackingModule : TrackerModule
/// Service to get item information
/// Service to get world information
/// Used to write logging information.
- public BossTrackingModule(Tracker tracker, IItemService itemService, IWorldService worldService, ILogger logger)
+ public BossTrackingModule(ITracker tracker, IItemService itemService, IWorldService worldService, ILogger logger)
: base(tracker, itemService, worldService, logger)
{
diff --git a/src/Randomizer.SMZ3.Tracking/VoiceCommands/ChatIntegrationModule.cs b/src/Randomizer.SMZ3.Tracking/VoiceCommands/ChatIntegrationModule.cs
index 3299a9e46..2c9c06c95 100644
--- a/src/Randomizer.SMZ3.Tracking/VoiceCommands/ChatIntegrationModule.cs
+++ b/src/Randomizer.SMZ3.Tracking/VoiceCommands/ChatIntegrationModule.cs
@@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
using Randomizer.Shared.Enums;
using Randomizer.SMZ3.ChatIntegration;
using Randomizer.SMZ3.ChatIntegration.Models;
@@ -45,7 +46,7 @@ public class ChatIntegrationModule : TrackerModule, IDisposable
///
/// The chat client to use.
/// Used to write logging information.
- public ChatIntegrationModule(Tracker tracker, IChatClient chatClient, IItemService itemService, IWorldService worldService, ITrackerTimerService timerService, ILogger logger)
+ public ChatIntegrationModule(ITracker tracker, IChatClient chatClient, IItemService itemService, IWorldService worldService, ITrackerTimerService timerService, ILogger logger)
: base(tracker, itemService, worldService, logger)
{
ChatClient = chatClient;
diff --git a/src/Randomizer.SMZ3.Tracking/VoiceCommands/CheatsModule.cs b/src/Randomizer.SMZ3.Tracking/VoiceCommands/CheatsModule.cs
index 5d5c041c9..3af68628b 100644
--- a/src/Randomizer.SMZ3.Tracking/VoiceCommands/CheatsModule.cs
+++ b/src/Randomizer.SMZ3.Tracking/VoiceCommands/CheatsModule.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Speech.Recognition;
using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
using Randomizer.Shared;
using Randomizer.SMZ3.Tracking.Services;
using Randomizer.Data.WorldData;
@@ -34,7 +35,7 @@ public class CheatsModule : TrackerModule
/// Service to get item information
/// Service to get world information
/// Used to write logging information.
- public CheatsModule(Tracker tracker, IItemService itemService, IWorldService worldService, ILogger logger)
+ public CheatsModule(ITracker tracker, IItemService itemService, IWorldService worldService, ILogger logger)
: base(tracker, itemService, worldService, logger)
{
diff --git a/src/Randomizer.SMZ3.Tracking/VoiceCommands/GoModeModule.cs b/src/Randomizer.SMZ3.Tracking/VoiceCommands/GoModeModule.cs
index 02bfbb8da..0082711da 100644
--- a/src/Randomizer.SMZ3.Tracking/VoiceCommands/GoModeModule.cs
+++ b/src/Randomizer.SMZ3.Tracking/VoiceCommands/GoModeModule.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
+using Randomizer.Abstractions;
using Randomizer.Data.Configuration.ConfigFiles;
using Randomizer.SMZ3.Tracking.Services;
@@ -21,7 +22,7 @@ public class GoModeModule : TrackerModule
/// Service to get world information
/// Used to log information.
///
- public GoModeModule(Tracker tracker, IItemService itemService, IWorldService worldService, ILogger logger, ResponseConfig responseConfig)
+ public GoModeModule(ITracker tracker, IItemService itemService, IWorldService worldService, ILogger logger, ResponseConfig responseConfig)
: base(tracker, itemService, worldService, logger)
{
_responseConfig = responseConfig;
diff --git a/src/Randomizer.SMZ3.Tracking/VoiceCommands/ItemTrackingModule.cs b/src/Randomizer.SMZ3.Tracking/VoiceCommands/ItemTrackingModule.cs
index bb7c4cf38..3772da801 100644
--- a/src/Randomizer.SMZ3.Tracking/VoiceCommands/ItemTrackingModule.cs
+++ b/src/Randomizer.SMZ3.Tracking/VoiceCommands/ItemTrackingModule.cs
@@ -2,7 +2,7 @@
using System.Speech.Recognition;
using Microsoft.Extensions.Logging;
-
+using Randomizer.Abstractions;
using Randomizer.SMZ3.Tracking.Services;
namespace Randomizer.SMZ3.Tracking.VoiceCommands
@@ -22,7 +22,7 @@ public class ItemTrackingModule : TrackerModule
///