diff --git a/alttp_sm_combo_randomizer_rom/src/ips_patches/sm/LiftOffOnGoalCompletionAllDungeons.asm b/alttp_sm_combo_randomizer_rom/src/ips_patches/sm/LiftOffOnGoalCompletionAllDungeons.asm
new file mode 100644
index 00000000..894dc6b1
--- /dev/null
+++ b/alttp_sm_combo_randomizer_rom/src/ips_patches/sm/LiftOffOnGoalCompletionAllDungeons.asm
@@ -0,0 +1,33 @@
+lorom
+
+org $a2aaa5
+ JSL check_goal
+
+org $92ef00
+check_goal:
+ PHX
+ PHY
+ PHP
+ REP #$30
+
+ LDA $A17B74 : AND #$0007 : CMP #$0007 : BNE .refuel ; require all pendants
+ LDA $A17B7A : AND #$007F : CMP #$007F : BNE .refuel ; require all crystals
+ LDA $7ED832 : AND #$000F : CMP #$000F : BNE .refuel ; required all boss tokens
+ LDA $A17BC5 : CMP #$0003 : BCC .refuel ; require post-aga world state
+ LDA $A17ADB : AND #$0020 : CMP #$0020 : BNE .refuel ; require aga2 defeated (pyramid hole open)
+
+ BRA .liftoff
+
+.refuel
+ PLP
+ PLY
+ PLX
+ CLC
+ RTL
+
+.liftoff
+ PLP
+ PLY
+ PLX
+ SEC
+ RTL
\ No newline at end of file
diff --git a/src/TrackerCouncil.Smz3.Abstractions/AutoTrackerBase.cs b/src/TrackerCouncil.Smz3.Abstractions/AutoTrackerBase.cs
index 6150c195..449c49e5 100644
--- a/src/TrackerCouncil.Smz3.Abstractions/AutoTrackerBase.cs
+++ b/src/TrackerCouncil.Smz3.Abstractions/AutoTrackerBase.cs
@@ -69,6 +69,11 @@ public abstract class AutoTrackerBase : IDisposable
///
public event EventHandler? GameChanged;
+ ///
+ /// Occurs when the tracker detects that the pyramid has been opened
+ ///
+ public event EventHandler? OpenPyramidChanged;
+
///
/// The action to run when the player asks Tracker to look at the game
///
@@ -99,6 +104,11 @@ public abstract class AutoTrackerBase : IDisposable
///
public bool PlayerHasFairy { get; set; }
+ ///
+ /// If the pyramid has been opened
+ ///
+ public bool OpenPyramid { get; set; }
+
///
/// If the user is activately in an SMZ3 rom
///
@@ -169,5 +179,13 @@ protected virtual void OnGameChanged()
GameChanged?.Invoke(this, EventArgs.Empty);
}
+ ///
+ /// Invokes the OpenPyramidChanged event
+ ///
+ public virtual void OnOpenPyramidChanged()
+ {
+ OpenPyramidChanged?.Invoke(this, EventArgs.Empty);
+ }
+
public abstract void Dispose();
}
diff --git a/src/TrackerCouncil.Smz3.Abstractions/AltGameModeBase.cs b/src/TrackerCouncil.Smz3.Abstractions/GameModeBase.cs
similarity index 51%
rename from src/TrackerCouncil.Smz3.Abstractions/AltGameModeBase.cs
rename to src/TrackerCouncil.Smz3.Abstractions/GameModeBase.cs
index 68a7c907..286243d6 100644
--- a/src/TrackerCouncil.Smz3.Abstractions/AltGameModeBase.cs
+++ b/src/TrackerCouncil.Smz3.Abstractions/GameModeBase.cs
@@ -1,5 +1,6 @@
using TrackerCouncil.Smz3.Data.GeneratedData;
using TrackerCouncil.Smz3.Data.Options;
+using TrackerCouncil.Smz3.Data.ParsedRom;
using TrackerCouncil.Smz3.Data.ViewModels;
using TrackerCouncil.Smz3.Data.WorldData;
using TrackerCouncil.Smz3.Shared.Enums;
@@ -7,30 +8,25 @@
namespace TrackerCouncil.Smz3.Abstractions;
-public abstract class AltGameModeBase
+public abstract class GameModeBase
{
public abstract bool IsComplete(World world);
- public virtual void UpdateWorld(World world, int seed, GameModeOptions gameModeOptions) {}
+ public abstract void UpdateWorld(World world, Random rng, GameModeOptions gameModeOptions);
- public virtual void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState) {}
+ public abstract void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState, ParsedRomDetails? parsedRomDetails);
public virtual void InitializeTracker(TrackerBase tracker) {}
- public virtual AltGameModeInGameText? GetInGameText(World world)
- {
- return null;
- }
+ public abstract GameModeInGameText GetInGameText(World world);
public virtual string? GetGameStartText(World world)
{
return null;
}
- public virtual bool IsKnowinglyComplete(World world)
- {
- return false;
- }
+ public abstract bool IsKnowinglyComplete(World world);
+
public virtual bool OnViewingPyramidText(World world)
{
@@ -42,13 +38,9 @@ public virtual bool OnViewingPyramidText(World world)
return null;
}
- public virtual List? GetGoalUiDetails(World world)
- {
- return null;
- }
+ public abstract List GetGoalUiDetails(World world, Progression progression);
- public virtual string GetSpoilerText(GameModeOptions gameModeOptions)
- {
- return string.Empty;
- }
+ public abstract string GetSpoilerText(GameModeOptions gameModeOptions);
+
+ public abstract Stream GetLiftOffOnGoalCompletionIpsPatch();
}
diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerAltGameModeService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerAltGameModeService.cs
deleted file mode 100644
index 9ed4d5c7..00000000
--- a/src/TrackerCouncil.Smz3.Abstractions/ITrackerAltGameModeService.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using TrackerCouncil.Smz3.Data.ViewModels;
-using TrackerCouncil.Smz3.Shared.Enums;
-
-namespace TrackerCouncil.Smz3.Abstractions;
-
-public interface ITrackerAltGameModeService
-{
- public GameModeType GameModeType { get; }
- public bool HasAltGameMode { get; }
- public bool IsAltGameModeComplete { get; }
- public event EventHandler? GoalStateChanged;
- public void MarkAltGameModeAsComplete();
- public bool IsAltGameModeKnowinglyComplete();
- public void OnViewingPyramidText();
- public void NotifyOfGoalStateChange();
- public List GetGoalUiDetails();
-}
diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameModeService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameModeService.cs
new file mode 100644
index 00000000..e9d1a47e
--- /dev/null
+++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameModeService.cs
@@ -0,0 +1,59 @@
+using TrackerCouncil.Smz3.Data.ViewModels;
+using TrackerCouncil.Smz3.Shared.Enums;
+
+namespace TrackerCouncil.Smz3.Abstractions;
+
+///
+/// Tracker service for various events that need to interact with the game mode
+///
+public interface ITrackerGameModeService
+{
+ ///
+ /// The current game mode type
+ ///
+ public GameModeType GameModeType { get; }
+
+ ///
+ /// If this is a game mode not handled by the base assembly code
+ ///
+ public bool HasAltGameMode { get; }
+
+ ///
+ /// If the game mode has been completed
+ ///
+ public bool IsGameModeComplete { get; }
+
+ ///
+ /// Event called when the goal progress has changed
+ ///
+ public event EventHandler? GoalStateChanged;
+
+ ///
+ /// Marks the game mode as complete
+ ///
+ public void MarkGameModeAsComplete();
+
+ ///
+ /// If the game mode is complete and the player should be aware of it
+ /// as they know the required amounts to be considered complete
+ ///
+ ///
+ public bool IsGameModeKnowinglyComplete();
+
+ ///
+ /// Informs the game mode that the player has viewed the pyramid so
+ /// that any unknown values are populated
+ ///
+ public void OnViewingPyramidText();
+
+ ///
+ /// Notifies all listeners that the progress of the game mode has changed
+ ///
+ public void NotifyOfGoalStateChange();
+
+ ///
+ /// Retrieve the UI to show the progress of the goals
+ ///
+ /// A list of goal details to show in the tracker UI
+ public List GetGoalUiDetails();
+}
diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs
index b24a44bc..8c505a6d 100644
--- a/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs
+++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs
@@ -14,11 +14,6 @@ public interface ITrackerGameStateService
///
public bool HasBeatenGame { get; }
- ///
- /// Gets if the local player has finished the goal
- ///
- public bool HasFinishedGoal { get; set; }
-
///
/// The last viewed hint tile or set of locations
///
diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerRewardService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerRewardService.cs
index 165c575b..a66a1dad 100644
--- a/src/TrackerCouncil.Smz3.Abstractions/ITrackerRewardService.cs
+++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerRewardService.cs
@@ -6,6 +6,11 @@ namespace TrackerCouncil.Smz3.Abstractions;
public interface ITrackerRewardService
{
+ ///
+ /// Event called when rewards have been added or removed
+ ///
+ public event EventHandler? RewardsChanged;
+
///
/// Sets the dungeon's reward to the specific pendant or crystal.
///
diff --git a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs
index 726df993..9c6dba67 100644
--- a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs
+++ b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs
@@ -54,7 +54,7 @@ public abstract class TrackerBase : IDisposable
public ITrackerSpoilerService SpoilerService { get; protected set; } = null!;
- public ITrackerAltGameModeService AltGameModeService { get; protected set; } = null!;
+ public ITrackerGameModeService GameModeService { get; protected set; } = null!;
///
/// Gets the world for the currently tracked playthrough.
diff --git a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs
index 8a28c4d5..37239e4b 100644
--- a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs
+++ b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs
@@ -36,7 +36,7 @@ public static class Program
(typeof(RoomConfig), "rooms.json"),
(typeof(UIConfig), "ui.json"),
(typeof(MetadataConfig), "metadata.json"),
- (typeof(AltGameModesConfig), "alt_game_modes.json"),
+ (typeof(GameModesConfig), "game_modes.json"),
};
private static IServiceProvider? _services;
@@ -221,11 +221,11 @@ private static void CreateTemplates(string outputPath)
var exampleMetadataConfig = MetadataConfig.Example();
WriteTemplate(templatePath, "metadata", templateMetadataConfig, exampleMetadataConfig);
- // Alt Game Modes Template
- var altGameModeConfig = new AltGameModesConfig();
- PopulateExample(altGameModeConfig, true);
- var exampleAltGameModeConfig = AltGameModesConfig.Example();
- WriteTemplate(templatePath, "alt_game_modes", altGameModeConfig, exampleAltGameModeConfig);
+ // Game Modes Template
+ var gameModeConfig = new GameModesConfig();
+ PopulateExample(gameModeConfig, true);
+ var exampleGameModeConfig = GameModesConfig.Example();
+ WriteTemplate(templatePath, "game_modes", gameModeConfig, exampleGameModeConfig);
}
private static void WriteTemplate(string templatePath, string type, object data, object? example)
diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/AltGameModesConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/GameModesConfig.cs
similarity index 56%
rename from src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/AltGameModesConfig.cs
rename to src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/GameModesConfig.cs
index 7040a4b5..357c143d 100644
--- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/AltGameModesConfig.cs
+++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/GameModesConfig.cs
@@ -6,11 +6,11 @@ namespace TrackerCouncil.Smz3.Data.Configuration.ConfigFiles;
[Description("Config file for various tracker lines and in game text for alt game modes.\r\n" +
"Note that all lines in game need to be no more than 19 characters.")]
-public class AltGameModesConfig : IMergeable, IConfigFile
+public class GameModesConfig : IMergeable, IConfigFile
{
- public static AltGameModesConfig Default()
+ public static GameModesConfig Default()
{
- return new AltGameModesConfig();
+ return new GameModesConfig();
}
///
@@ -35,11 +35,32 @@ public static AltGameModesConfig Default()
///
public SchrodingersString? SpazerHuntGameStarted { get; set; }
+ ///
+ /// In game text for the intro to the Ganon battle for All Dungeons mode
+ ///
+ public SchrodingersString? AllDungeonsGanonIntro { get; set; }
+
+ ///
+ /// In game text for the intro to the Ganon battle when you have not met the requirements for All Dungeons mode
+ ///
+ public SchrodingersString? AllDungeonsGanonIntroGoalsNotMet { get; set; }
+
+ ///
+ /// In game text for the sign on Ganon's pyramid to explain the goal for All Dungeons mode
+ ///
+ public SchrodingersString? AllDungeonsGanonGoalSign { get; set; }
+
+ ///
+ /// Tracker line for when starting a new game in All Dungeons mode
+ ///
+ public SchrodingersString? AllDungeonsGameStarted { get; set; }
+
public static object Example()
{
return new Dictionary()
{
- { "SpazerHuntGanonIntro", new SchrodingersString("In game text with\neach line being\n19 characters\nor less", new SchrodingersString.Possibility("Another example\nof in game text", 0.1)) }
+ { "SpazerHuntGanonIntro", new SchrodingersString("In game text with\neach line being\n19 characters\nor less", new SchrodingersString.Possibility("Another example\nof in game text", 0.1)) },
+ { "AllDungeonsGanonIntro", new SchrodingersString("In game text with\neach line being\n19 characters\nor less", new SchrodingersString.Possibility("Another example\nof in game text", 0.1)) }
};
}
}
diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs
index 0f78bf94..4826776b 100644
--- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs
+++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigProvider.cs
@@ -292,13 +292,13 @@ public virtual MetadataConfig GetMetadataConfig(IReadOnlyCollection prof
LoadYamlConfigs("metadata.yml", profiles, mood);
///
- /// Returns the configs with alt game mode text
+ /// Returns the configs with game mode text
///
/// The selected tracker profile(s) to load
/// The current tracker mood to pick the specific mood file
///
- public virtual AltGameModesConfig GetAltGameModesConfig(IReadOnlyCollection profiles, string? mood) =>
- LoadYamlConfigs("alt_game_modes.yml", profiles, mood);
+ public virtual GameModesConfig GetGameModesConfig(IReadOnlyCollection profiles, string? mood) =>
+ LoadYamlConfigs("game_modes.yml", profiles, mood);
///
/// Returns a collection of all possible config profiles to
diff --git a/src/TrackerCouncil.Smz3.Data/GeneratedData/AltGameModeInGameText.cs b/src/TrackerCouncil.Smz3.Data/GeneratedData/AltGameModeInGameText.cs
deleted file mode 100644
index 00ed8356..00000000
--- a/src/TrackerCouncil.Smz3.Data/GeneratedData/AltGameModeInGameText.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace TrackerCouncil.Smz3.Data.GeneratedData;
-
-public class AltGameModeInGameText
-{
- public string? GanonIntro { get; set; }
- public string? GanonIntroGoalsNotMet { get; set; }
- public string? GanonGoalSign { get; set; }
-}
diff --git a/src/TrackerCouncil.Smz3.Data/GeneratedData/GameModeInGameText.cs b/src/TrackerCouncil.Smz3.Data/GeneratedData/GameModeInGameText.cs
new file mode 100644
index 00000000..b2430111
--- /dev/null
+++ b/src/TrackerCouncil.Smz3.Data/GeneratedData/GameModeInGameText.cs
@@ -0,0 +1,10 @@
+using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes;
+
+namespace TrackerCouncil.Smz3.Data.GeneratedData;
+
+public class GameModeInGameText
+{
+ public required string? GanonIntro { get; set; }
+ public required string? GanonIntroGoalsNotMet { get; set; }
+ public required string? GanonGoalSign { get; set; }
+}
diff --git a/src/TrackerCouncil.Smz3.Data/Options/GameModeOptions.cs b/src/TrackerCouncil.Smz3.Data/Options/GameModeOptions.cs
index a7483e32..79ee5042 100644
--- a/src/TrackerCouncil.Smz3.Data/Options/GameModeOptions.cs
+++ b/src/TrackerCouncil.Smz3.Data/Options/GameModeOptions.cs
@@ -34,47 +34,13 @@ public class GameModeOptions
public bool ShuffleMetroidBossTokens { get; set; }
public bool OpenPyramid { get; set; }
- public void Randomize(Random rnd)
+ public GameModeOptions Clone()
{
- if (!RandomizeNumericAmounts)
- {
- return;
- }
-
- if (MaxGanonsTowerCrystalCount > MinGanonsTowerCrystalCount)
- {
- GanonsTowerCrystalCount = rnd.Next(MinGanonsTowerCrystalCount, MaxGanonsTowerCrystalCount + 1);
- }
- else
- {
- GanonsTowerCrystalCount = MinGanonsTowerCrystalCount;
- }
-
- // Only randomize vanilla crystals as alt game modes should do their own randomization
- if (SelectedGameModeType == GameModeType.Vanilla)
- {
- if (MaxGanonCrystalCount > MinGanonCrystalCount)
- {
- GanonCrystalCount = rnd.Next(MinGanonCrystalCount, MaxGanonCrystalCount + 1);
- }
- else
- {
- GanonCrystalCount = MinGanonCrystalCount;
- }
-
- if (MaxTourianBossCount > MinTourianBossCount)
- {
- TourianBossCount = rnd.Next(MinTourianBossCount, MaxTourianBossCount + 1);
- }
- else
- {
- TourianBossCount = MinTourianBossCount;
- }
- }
+ return (GameModeOptions)MemberwiseClone();
}
- public GameModeOptions Clone()
+ public bool IsAltGameMode()
{
- return (GameModeOptions)MemberwiseClone();
+ return SelectedGameModeType != GameModeType.Vanilla && SelectedGameModeType != GameModeType.AllDungeons;
}
}
diff --git a/src/TrackerCouncil.Smz3.Data/ParsedRom/ParsedRomDetails.cs b/src/TrackerCouncil.Smz3.Data/ParsedRom/ParsedRomDetails.cs
index e86f3977..c6b4011f 100644
--- a/src/TrackerCouncil.Smz3.Data/ParsedRom/ParsedRomDetails.cs
+++ b/src/TrackerCouncil.Smz3.Data/ParsedRom/ParsedRomDetails.cs
@@ -19,6 +19,7 @@ public class ParsedRomDetails
public required bool OpenPyarmid { get; set; }
public required bool SkipTourianBossDoor { get; set; }
public required bool SpinJumpAnimations { get; set; }
+ public required GameModeType GameModeType { get; set; }
public required List Players { get; set; }
public required List Locations { get; set; }
public required List Bosses { get; set; }
diff --git a/src/TrackerCouncil.Smz3.Data/Services/GenerationSettingsWindowService.cs b/src/TrackerCouncil.Smz3.Data/Services/GenerationSettingsWindowService.cs
index b7ce1c8f..e471641c 100644
--- a/src/TrackerCouncil.Smz3.Data/Services/GenerationSettingsWindowService.cs
+++ b/src/TrackerCouncil.Smz3.Data/Services/GenerationSettingsWindowService.cs
@@ -41,6 +41,7 @@ public GenerationWindowViewModel GetViewModel(Dictionary g
_model.Basic.SelectedPreset = _model.Basic.Presets.First();
_model.Basic.MsuShuffleStyle = _options.PatchOptions.MsuShuffleStyle;
_model.Basic.MsuRandomizationStyle = _options.PatchOptions.MsuRandomizationStyle;
+ _model.Basic.LiftOffOnGoalCompletion = _options.SeedOptions.GameModeOptions.LiftOffOnGoalCompletion;
_model.GameSettings.RandomizeNumericValues = _options.SeedOptions.GameModeOptions.RandomizeNumericAmounts;
_model.GameSettings.GoalSettings.GameModeTypeDescriptions = gameModeDescriptions;
@@ -215,6 +216,11 @@ public void SaveSettings()
_options.PatchOptions.MetroidControls.AimUp = _model.Customizations.AimUpButton;
_options.PatchOptions.MetroidControls.AimDown = _model.Customizations.AimDownButton;
+ if (_model.IsImportMode)
+ {
+ _options.SeedOptions.GameModeOptions.LiftOffOnGoalCompletion = _model.Basic.LiftOffOnGoalCompletion;
+ }
+
_options.Save();
}
diff --git a/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs b/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs
index 7e8c3cf9..a901bc67 100644
--- a/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs
+++ b/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs
@@ -83,7 +83,7 @@ public interface IMetadataService
///
/// Lines for alt game modes
///
- public AltGameModesConfig AltGameModes { get; }
+ public GameModesConfig GameModes { get; }
///
/// The current selected mood
diff --git a/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs b/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs
index fb2aa7ea..73e8a54f 100644
--- a/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs
+++ b/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs
@@ -61,7 +61,7 @@ public MetadataService(ConfigProvider configs, ILogger logger,
public UIConfig UILayouts { get; private set; } = null!;
- public AltGameModesConfig AltGameModes { get; private set; } = null!;
+ public GameModesConfig GameModes { get; private set; } = null!;
public string Mood { get; private set; } = null!;
@@ -111,7 +111,7 @@ public void ReloadConfigs()
Rewards = _configProvider.GetRewardConfig(profiles, Mood);
Rooms = _configProvider.GetRoomConfig(profiles, Mood);
UILayouts = _configProvider.GetUIConfig(profiles, Mood);
- AltGameModes = _configProvider.GetAltGameModesConfig(profiles, Mood);
+ GameModes = _configProvider.GetGameModesConfig(profiles, Mood);
}
else
{
@@ -129,7 +129,7 @@ public void ReloadConfigs()
Rewards = IMergeable.Combine(_configProvider.GetRewardConfig(profiles, Mood), TrackerSpriteProfile.RewardConfig);
Rooms = IMergeable.Combine(_configProvider.GetRoomConfig(profiles, Mood), TrackerSpriteProfile.RoomConfig);
UILayouts = _configProvider.GetUIConfig(profiles, Mood);
- AltGameModes = _configProvider.GetAltGameModesConfig(profiles, Mood);
+ GameModes = _configProvider.GetGameModesConfig(profiles, Mood);
}
}
diff --git a/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowBasicViewModel.cs b/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowBasicViewModel.cs
index fc64010b..ac7dd3b0 100644
--- a/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowBasicViewModel.cs
+++ b/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowBasicViewModel.cs
@@ -120,6 +120,12 @@ public MsuShuffleStyle MsuShuffleStyle
public bool IsShuffleStyleVisible =>
MsuRandomizationStyle is MSURandomizerLibrary.MsuRandomizationStyle.Continuous or MSURandomizerLibrary.MsuRandomizationStyle.Shuffled;
+ public bool LiftOffOnGoalCompletion
+ {
+ get;
+ set => SetField(ref field, value);
+ }
+
public List MsuPaths
{
get => _msuPaths;
diff --git a/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowGoalSettingsViewModel..cs b/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowGoalSettingsViewModel..cs
index ea9702c7..20f80794 100644
--- a/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowGoalSettingsViewModel..cs
+++ b/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowGoalSettingsViewModel..cs
@@ -223,7 +223,6 @@ public bool RandomizeGoalCounts
public bool ShowRandomizedSpazerCounts => RandomizeGoalCounts && IsSpazerHunt;
public bool StaticGoalCounts => !RandomizeGoalCounts;
public bool IsDefaultGameMode => SelectedGameModeType == GameModeType.Vanilla;
- public bool IsAltGameMode => SelectedGameModeType != GameModeType.Vanilla;
public bool IsSpazerHunt => SelectedGameModeType == GameModeType.SpazerHunt;
public Dictionary GameModeTypeDescriptions = new();
diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Progression.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Progression.cs
index bd363890..d1f1fa33 100644
--- a/src/TrackerCouncil.Smz3.Data/WorldData/Progression.cs
+++ b/src/TrackerCouncil.Smz3.Data/WorldData/Progression.cs
@@ -122,7 +122,8 @@ public Progression(IEnumerable- items, IEnumerable rewards, IEnumer
public int Rupees => GetRupeeCount();
public bool Agahnim => Rewards.Contains(RewardType.Agahnim);
public bool GreenPendant => Rewards.Contains(RewardType.PendantGreen);
- public bool AllPendants => Rewards.Count(r => r is RewardType.PendantGreen or RewardType.PendantRed or RewardType.PendantBlue) >= 3;
+ public int PendantCount => Rewards.Count(r => r is RewardType.PendantGreen or RewardType.PendantRed or RewardType.PendantBlue);
+ public bool AllPendants => PendantCount >= 3;
public bool BothRedCrystals => Rewards.Count(r => r == RewardType.CrystalRed) >= 2;
public bool AllCrystals => CrystalCount >= 7;
public bool Kraid => DefeatedBosses.Contains(BossType.Kraid);
diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs
index 15a1b419..eff047e9 100644
--- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs
+++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs
@@ -93,7 +93,9 @@ public void UpdateGanonAccessibility(Progression progression, Progression assume
var isPyramidOpen = World.Config.GameModeOptions.OpenPyramid || canClimbGt;
var hasMetGoal = hasAltGameMode
? isAltGameModeComplete
- : numAcquiredCrystals >= ganonCrystalRequirement;
+ : World.Config.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla
+ ? numAcquiredCrystals >= ganonCrystalRequirement
+ : progression is { AllCrystals: true, AllPendants: true, MetroidBossCount: >= 4, Agahnim: true };
var canHurtGanon = hasMetGoal && progression.MasterSword &&
(progression.Lamp || progression.FireRod);
@@ -113,8 +115,11 @@ public void UpdateGanonAccessibility(Progression progression, Progression assume
assumedKeyProgression.LegacyProgression)));
var isPyramidOpen = World.Config.GameModeOptions.OpenPyramid || canClimbGt;
- var canHurtGanon = numAcquiredCrystals >= ganonCrystalRequirement && progression.MasterSword &&
- (progression.Lamp || progression.FireRod);
+ var hasMetGoal = World.Config.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla
+ ? numAcquiredCrystals >= ganonCrystalRequirement
+ : progression is { AllCrystals: true, AllPendants: true, MetroidBossCount: >= 4, Agahnim: true };
+
+ var canHurtGanon = hasMetGoal && progression.MasterSword && (progression.Lamp || progression.FireRod);
NewAccessibility = canAccessPyramid && isPyramidOpen && canHurtGanon
? Accessibility.Available
diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/World.cs b/src/TrackerCouncil.Smz3.Data/WorldData/World.cs
index bae76ff4..0bf173cb 100644
--- a/src/TrackerCouncil.Smz3.Data/WorldData/World.cs
+++ b/src/TrackerCouncil.Smz3.Data/WorldData/World.cs
@@ -273,7 +273,6 @@ public Boss GetBossOfType(BossType type)
public void Setup(Random rnd)
{
- Config.GameModeOptions.Randomize(rnd);
SetMedallions(rnd);
SetRewards(rnd);
SetBottles(rnd);
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/IpsPatches/IpsPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/IpsPatches/IpsPatch.cs
index f9301499..5a121610 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/IpsPatches/IpsPatch.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/IpsPatches/IpsPatch.cs
@@ -224,5 +224,11 @@ public static Stream GetStream(string name)
/// A new stream that contains the IPS patch.
public static Stream LiftOffOnGoalCompletionAlt() => GetStream("LiftOffOnGoalCompletionAlt.ips");
+ ///
+ /// Gets a stream for the IPS patch to allow you to lift off from ship after completing goal
+ ///
+ /// A new stream that contains the IPS patch.
+ public static Stream LiftOffOnGoalCompletionAllDungeons() => GetStream("LiftOffOnGoalCompletionAllDungeons.ips");
+
}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/IpsPatches/LiftOffOnGoalCompletionAllDungeons.ips b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/IpsPatches/LiftOffOnGoalCompletionAllDungeons.ips
new file mode 100644
index 00000000..230df9dc
Binary files /dev/null and b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/IpsPatches/LiftOffOnGoalCompletionAllDungeons.ips differ
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/GoalsPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/GoalsPatch.cs
index f3ac2a9b..9dfff858 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/GoalsPatch.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/GoalsPatch.cs
@@ -5,11 +5,17 @@
namespace TrackerCouncil.Smz3.SeedGenerator.FileData.Patches;
-[SkipForParsedRoms]
public class GoalsPatch : RomPatch
{
public override IEnumerable GetChanges(GetPatchesRequest data)
{
+ if (data.IsParsedRom && data.Config.ParsedRomDetails!.RomGenerator != RomGenerator.Cas)
+ {
+ yield return new GeneratedPatch(Snes(0xF47008), [(byte)data.Config.ParsedRomDetails.TourianBossCount, 0x00]);
+ yield return new GeneratedPatch(Snes(0xF47012), [(byte)data.Config.ParsedRomDetails.GanonCrystalCount, 0x00]);
+ yield break;
+ }
+
// Open pyramid
if (data.Config.GameModeOptions.OpenPyramid)
{
@@ -22,11 +28,12 @@ public override IEnumerable GetChanges(GetPatchesRequest data)
yield return new GeneratedPatch(0x40008C, gtCrystals == 0 ? [0x01] : [0x00]);
// Crystals to damage Ganon
- yield return new GeneratedPatch(0x40005F, [(byte)data.Config.GameModeOptions.GanonCrystalCount]);
+ var ganonCrystals = data.Config.GameModeOptions.SelectedGameModeType == GameModeType.AllDungeons ? 7 : data.Config.GameModeOptions.GanonCrystalCount;
+ yield return new GeneratedPatch(0x40005F, [(byte)ganonCrystals]);
yield return new GeneratedPatch(Snes(0xF47012), [(byte)data.Config.GameModeOptions.GanonCrystalCount, 0x00]);
// Bosses to enter Tourian
- var numBosses = data.Config.GameModeOptions.TourianBossCount;
+ var numBosses = data.Config.GameModeOptions.SelectedGameModeType == GameModeType.AllDungeons ? 4 : data.Config.GameModeOptions.TourianBossCount;
yield return new GeneratedPatch(Snes(0xF47008), [(byte)numBosses, 0x00]);
yield return new GeneratedPatch(Snes(0xF4700B), numBosses == 0 ? [0x00, 0x04] : [0x00, 0x01]);
@@ -39,6 +46,10 @@ public override IEnumerable GetChanges(GetPatchesRequest data)
{
yield return new GeneratedPatch(Snes(0x30803E), [0x03]);
}
+ else if (data.Config.GameModeOptions.SelectedGameModeType == GameModeType.AllDungeons)
+ {
+ yield return new GeneratedPatch(Snes(0x30803E), [0x02]);
+ }
else
{
yield return new GeneratedPatch(Snes(0x30803E), [0x05]);
@@ -72,4 +83,17 @@ public static bool GetOpenPyramid(byte[] rom)
{
return rom[Snes(0x30808B)] == 0x01;
}
+
+ public static GameModeType GetGameModeType(byte[] rom)
+ {
+ var val = rom[Snes(0x30803E)];
+ if (val == 0x02)
+ {
+ return GameModeType.AllDungeons;
+ }
+ else
+ {
+ return GameModeType.Vanilla;
+ }
+ }
}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaTextsPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaTextsPatch.cs
index 6a90f17a..532e2a48 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaTextsPatch.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaTextsPatch.cs
@@ -9,8 +9,8 @@
using TrackerCouncil.Smz3.Data.Services;
using TrackerCouncil.Smz3.Data.WorldData;
using TrackerCouncil.Smz3.Data.WorldData.Regions;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.Contracts;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.SeedGenerator.Text;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Shared.Models;
@@ -18,7 +18,7 @@
namespace TrackerCouncil.Smz3.SeedGenerator.FileData.Patches;
[Order(-5)]
-public class ZeldaTextsPatch(IMetadataService metadataService, IGameHintService gameHintService, AltGameModeFactory altGameModeFactory) : RomPatch
+public class ZeldaTextsPatch(IMetadataService metadataService, IGameHintService gameHintService, GameModeFactory gameModeFactory) : RomPatch
{
private StringTable _stringTable = null!;
private PlandoTextConfig _plandoText = null!;
@@ -74,7 +74,7 @@ private IEnumerable GetFullTextPatchList(GetPatchesRequest data)
{
var regions = data.World.Regions.OfType().ToList();
- var altGameModeText = altGameModeFactory.GetInGameText(data.World);
+ var gameModeText = gameModeFactory.GetInGameText(data.World);
var greenPendantDungeon = regions
.Where(x => x.RewardType == RewardType.PendantGreen)
@@ -100,7 +100,7 @@ private IEnumerable GetFullTextPatchList(GetPatchesRequest data)
yield return SetText(0x308C00, StringTable.TavernMan,
GameLines.TavernMan, _plandoText.TavernMan);
- foreach (var text in GanonText(data, altGameModeText))
+ foreach (var text in GanonText(data, gameModeText))
{
yield return text;
}
@@ -123,9 +123,7 @@ private IEnumerable GetFullTextPatchList(GetPatchesRequest data)
SetText(StringTable.GanonsTowerGoalSign,
GameLines.GanonsTowerGoalSign, _plandoText.GanonsTowerGoalSign, false, gtCrystalCount == 1 ? "1 crystal" : $"{gtCrystalCount} crystals");
- var ganonCrystalCount = data.World.Config.GameModeOptions.GanonCrystalCount;
- SetText(StringTable.GanonGoalSign,
- GameLines.GanonGoalSign, _plandoText.GanonGoalSign ?? altGameModeText?.GanonGoalSign, false, ganonCrystalCount == 1 ? "1 crystal" : $"{ganonCrystalCount} crystals");
+ SetText(StringTable.GanonGoalSign, null, _plandoText.GanonGoalSign ?? gameModeText?.GanonGoalSign);
if (data.World.HintTiles.Any() || _plandoText.HasHintTileText)
{
@@ -213,13 +211,10 @@ private void SetChoiceText(string textKey, SchrodingersString? defaultText, stri
}
- private IEnumerable GanonText(GetPatchesRequest data, AltGameModeInGameText? altGameModeInGameText)
+ private IEnumerable GanonText(GetPatchesRequest data, GameModeInGameText? gameModeInGameText)
{
- yield return SetText(0x308600, StringTable.GanonIntro,
- GameLines.GanonIntro, _plandoText.GanonIntro ?? altGameModeInGameText?.GanonIntro);
-
- yield return SetText(0x309100, StringTable.GanonIntroAlt,
- GameLines.GanonIntroGoalsNotMet, _plandoText.GanonIntroGoalsNotMet ?? altGameModeInGameText?.GanonIntroGoalsNotMet);
+ yield return SetText(0x308600, StringTable.GanonIntro, null, _plandoText.GanonIntro ?? gameModeInGameText?.GanonIntro);
+ yield return SetText(0x309100, StringTable.GanonIntroAlt, null, _plandoText.GanonIntroGoalsNotMet ?? gameModeInGameText?.GanonIntroGoalsNotMet);
var silversLocation = data.Worlds.SelectMany(world => world.Locations)
.FirstOrDefault(l => l.ItemIs(ItemType.SilverArrows, data.World));
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/AllDungeonsGameMode.cs b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/AllDungeonsGameMode.cs
new file mode 100644
index 00000000..302afb3d
--- /dev/null
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/AllDungeonsGameMode.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using TrackerCouncil.Smz3.Abstractions;
+using TrackerCouncil.Smz3.Data.GeneratedData;
+using TrackerCouncil.Smz3.Data.Options;
+using TrackerCouncil.Smz3.Data.ParsedRom;
+using TrackerCouncil.Smz3.Data.Services;
+using TrackerCouncil.Smz3.Data.Tracking;
+using TrackerCouncil.Smz3.Data.ViewModels;
+using TrackerCouncil.Smz3.Data.WorldData;
+using TrackerCouncil.Smz3.SeedGenerator.FileData.IpsPatches;
+using TrackerCouncil.Smz3.Shared.Enums;
+using TrackerCouncil.Smz3.Shared.Models;
+
+namespace TrackerCouncil.Smz3.SeedGenerator.GameModes;
+
+[GameModeType(GameModeType.AllDungeons)]
+[Description("Complete all dungeons including Castle Tower and Ganon's Tower and defeat all 4 main Metroid bosses to be able to defeat Ganon. Defeat all 4 main bosses in Metroid to be able to enter Tourian and defeat Mother Brain.")]
+public class AllDungeonsGameMode(IMetadataService metadata) : GameModeBase
+{
+ private TrackerBase _tracker = null!;
+
+ public override void InitializeTracker(TrackerBase tracker)
+ {
+ _tracker = tracker;
+ _tracker.BossTracker.BossUpdated += BossTrackerOnBossUpdated;
+ _tracker.RewardTracker.RewardsChanged += RewardTrackerOnRewardsChanged;
+ }
+
+ private void RewardTrackerOnRewardsChanged(object? sender, EventArgs e)
+ {
+ if (IsComplete(_tracker.World))
+ {
+ _tracker.GameModeService.MarkGameModeAsComplete();
+ }
+
+ _tracker.GameModeService.NotifyOfGoalStateChange();
+ }
+
+ private void BossTrackerOnBossUpdated(object? sender, BossTrackedEventArgs e)
+ {
+ if (IsComplete(_tracker.World))
+ {
+ _tracker.GameModeService.MarkGameModeAsComplete();
+ }
+
+ _tracker.GameModeService.NotifyOfGoalStateChange();
+ }
+
+ public override bool IsComplete(World world)
+ {
+ if (world.State == null)
+ {
+ return false;
+ }
+
+ var progression = _tracker.PlayerProgressionService.GetProgression(false);
+
+ return progression is { AllPendants: true, AllCrystals: true } &&
+ progression.MetroidBossCount >= 4 &&
+ world.AllBosses.First(x => x.Type == BossType.Agahnim).Defeated &&
+ (_tracker.AutoTracker?.OpenPyramid == true || world.AllBosses.First(x => x.Type == BossType.Ganon).Defeated || world.Config.GameModeOptions.OpenPyramid);
+ }
+
+ public override void UpdateWorld(World world, Random rng, GameModeOptions gameModeOptions)
+ {
+ gameModeOptions.GanonCrystalCount = 7;
+ gameModeOptions.TourianBossCount = 4;
+ gameModeOptions.OpenPyramid = false;
+ }
+
+ public override void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState,
+ ParsedRomDetails? parsedRomDetails)
+ {
+ trackerState.MarkedGanonCrystalCount = 7;
+ trackerState.MarkedTourianBossCount = 4;
+ }
+
+ public override GameModeInGameText GetInGameText(World world)
+ {
+ return new GameModeInGameText
+ {
+ GanonIntro = metadata.GameModes.AllDungeonsGanonIntro,
+ GanonIntroGoalsNotMet = metadata.GameModes.AllDungeonsGanonIntroGoalsNotMet,
+ GanonGoalSign = metadata.GameModes.AllDungeonsGanonGoalSign,
+ };
+ }
+
+ public override string? GetGameStartText(World world)
+ {
+ return metadata.GameModes.AllDungeonsGameStarted?.ToString();
+ }
+
+ public override bool IsKnowinglyComplete(World world)
+ {
+ return IsComplete(world);
+ }
+
+ public override List GetGoalUiDetails(World world, Progression progression)
+ {
+ var dungeons = progression.CrystalCount + progression.PendantCount;
+
+ if (world.AllBosses.First(x => x.Type == BossType.Agahnim).Defeated)
+ {
+ dungeons++;
+ }
+
+ if (_tracker.AutoTracker?.OpenPyramid == true || world.AllBosses.First(x => x.Type == BossType.Ganon).Defeated || world.Config.GameModeOptions.OpenPyramid)
+ {
+ dungeons++;
+ }
+
+ var bosses = progression.MetroidBossCount;
+ return
+ [
+ new GoalUiDetails { IconCategory = "Items", Icon = "map_full.png", Text = $"{dungeons}/12" },
+ new GoalUiDetails { IconCategory = "Dungeons", Icon = "metroid boss token.png", Text = $"{bosses}/4" }
+ ];
+ }
+
+ public override string GetSpoilerText(GameModeOptions gameModeOptions)
+ {
+ return "";
+ }
+
+ public override Stream GetLiftOffOnGoalCompletionIpsPatch()
+ {
+ return IpsPatch.LiftOffOnGoalCompletionAllDungeons();
+ }
+}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/AltGameModes/AltGameModeFactory.cs b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/GameModeFactory.cs
similarity index 59%
rename from src/TrackerCouncil.Smz3.SeedGenerator/AltGameModes/AltGameModeFactory.cs
rename to src/TrackerCouncil.Smz3.SeedGenerator/GameModes/GameModeFactory.cs
index f7d165e9..87de0db8 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/AltGameModes/AltGameModeFactory.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/GameModeFactory.cs
@@ -1,27 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.IO;
using System.Reflection;
using TrackerCouncil.Smz3.Abstractions;
using TrackerCouncil.Smz3.Data.GeneratedData;
using TrackerCouncil.Smz3.Data.Options;
+using TrackerCouncil.Smz3.Data.ParsedRom;
using TrackerCouncil.Smz3.Data.WorldData;
+using TrackerCouncil.Smz3.Shared;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Shared.Models;
-namespace TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
+namespace TrackerCouncil.Smz3.SeedGenerator.GameModes;
-public class AltGameModeFactory(IServiceProvider serviceProvider)
+public class GameModeFactory(IServiceProvider serviceProvider)
{
private static readonly Dictionary s_gameModeTypes = [];
-
- private static readonly Dictionary s_gameModeDescriptions = new()
- {
- {
- GameModeType.Vanilla,
- "Complete Zelda dungeons to obtain crystals to enter Ganon's Tower and defeat Ganon. Defeat bosses in Super Metroid to be able to enter Tourian and defeat Mother Brain."
- }
- };
+ private static readonly Dictionary s_gameModeDescriptions = new();
public static void AddGameModeClass(Type gameModeType)
{
@@ -41,14 +37,14 @@ public static void AddGameModeClass(Type gameModeType)
s_gameModeDescriptions.Add(gameModeTypeAttribute.GameModeType, descriptionAttribute.Description);
}
- public AltGameModeBase GetGameMode(GameModeType gameMode)
+ public GameModeBase GetGameMode(GameModeType gameMode)
{
if (!s_gameModeTypes.TryGetValue(gameMode, out var gameModeClassType))
{
throw new InvalidOperationException($"GameModeType {gameMode} does not have a matching GameMode class");
}
- return serviceProvider.GetService(gameModeClassType) as AltGameModeBase ??
+ return serviceProvider.GetService(gameModeClassType) as GameModeBase ??
throw new InvalidOperationException($"GameModeType {gameMode} does not have a valid GameMode class");
}
@@ -56,36 +52,31 @@ public World UpdateWorld(World world, int seed)
{
var gameModeOptions = world.Config.GameModeOptions;
- if (gameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
+ var rng = new Random(seed);
+ rng.Sanitize();
+
+ var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
+
+ gameMode.UpdateWorld(world, rng, gameModeOptions);
+
+ if (gameModeOptions.RandomizeNumericAmounts)
{
- return world;
+ gameModeOptions.GanonCrystalCount = rng.Next(gameModeOptions.MinGanonCrystalCount, gameModeOptions.MaxGanonCrystalCount + 1);
+ gameModeOptions.TourianBossCount = rng.Next(gameModeOptions.MinTourianBossCount, gameModeOptions.MaxTourianBossCount + 1);
}
- var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
- gameMode.UpdateWorld(world, seed, gameModeOptions);
return world;
}
public string? GetGameStartText(World world)
{
var gameModeOptions = world.Config.GameModeOptions;
-
- if (gameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
- {
- return null;
- }
-
var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
return gameMode.GetGameStartText(world);
}
- public AltGameModeInGameText? GetInGameText(World world)
+ public GameModeInGameText GetInGameText(World world)
{
- if (world.Config.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
- {
- return null;
- }
-
var gameMode = GetGameMode(world.Config.GameModeOptions.SelectedGameModeType);
return gameMode.GetInGameText(world);
}
@@ -95,45 +86,47 @@ public Dictionary GetGameModeDescriptions()
return s_gameModeDescriptions;
}
- public void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState)
+ public void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState, ParsedRomDetails? parsedRomDetails)
{
- if (gameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
+ var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
+ gameMode.UpdateInitialTrackerState(gameModeOptions, trackerState, parsedRomDetails);
+
+ int? markedGanonsTowerCrystalCount = null;
+
+ if (parsedRomDetails == null)
{
- return;
+ if (gameModeOptions.RandomizeNumericAmounts)
+ {
+ markedGanonsTowerCrystalCount =
+ gameModeOptions.MinGanonsTowerCrystalCount == gameModeOptions.MaxGanonsTowerCrystalCount
+ ? gameModeOptions.MinGanonsTowerCrystalCount
+ : null;
+ }
+ else
+ {
+ markedGanonsTowerCrystalCount = gameModeOptions.GanonsTowerCrystalCount;
+ }
}
- var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
- gameMode.UpdateInitialTrackerState(gameModeOptions, trackerState);
+ trackerState.MarkedGanonsTowerCrystalCount = markedGanonsTowerCrystalCount;
}
public string GetSpoilerText(GameModeOptions gameModeOptions)
{
-
- string goalText;
-
- if (gameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
- {
- goalText =
- $"GanonCrystalCount = {gameModeOptions.GanonCrystalCount}, TourianBossCount = {gameModeOptions.TourianBossCount}";
- }
- else
- {
- var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
- goalText = gameMode.GetSpoilerText(gameModeOptions);
- }
-
- return
- $"Goal: {gameModeOptions.SelectedGameModeType} | {goalText}, LiftOffOnGoalCompletion = {gameModeOptions.LiftOffOnGoalCompletion}";
+ var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
+ var goalText = gameMode.GetSpoilerText(gameModeOptions);
+ return $"Goal: {gameModeOptions.SelectedGameModeType} | {goalText}, LiftOffOnGoalCompletion = {gameModeOptions.LiftOffOnGoalCompletion}";
}
public List? GetGameModeLocations(World world, List allWorlds)
{
- if (world.Config.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
- {
- return null;
- }
-
var gameMode = GetGameMode(world.Config.GameModeOptions.SelectedGameModeType);
return gameMode.GetGameModeLocations(world, allWorlds);
}
+
+ public Stream GetLiftOffOnGoalCompletionIpsPatch(GameModeOptions gameModeOptions)
+ {
+ var gameMode = GetGameMode(gameModeOptions.SelectedGameModeType);
+ return gameMode.GetLiftOffOnGoalCompletionIpsPatch();
+ }
}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/AltGameModes/SpazerHuntAltGameMode.cs b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/SpazerHuntGameMode.cs
similarity index 84%
rename from src/TrackerCouncil.Smz3.SeedGenerator/AltGameModes/SpazerHuntAltGameMode.cs
rename to src/TrackerCouncil.Smz3.SeedGenerator/GameModes/SpazerHuntGameMode.cs
index 631b6035..6b984dfe 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/AltGameModes/SpazerHuntAltGameMode.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/SpazerHuntGameMode.cs
@@ -1,23 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.IO;
using System.Linq;
using TrackerCouncil.Smz3.Abstractions;
using TrackerCouncil.Smz3.Data.GeneratedData;
using TrackerCouncil.Smz3.Data.Options;
+using TrackerCouncil.Smz3.Data.ParsedRom;
using TrackerCouncil.Smz3.Data.Services;
using TrackerCouncil.Smz3.Data.Tracking;
using TrackerCouncil.Smz3.Data.ViewModels;
using TrackerCouncil.Smz3.Data.WorldData;
-using TrackerCouncil.Smz3.Shared;
+using TrackerCouncil.Smz3.SeedGenerator.FileData.IpsPatches;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Shared.Models;
-namespace TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
+namespace TrackerCouncil.Smz3.SeedGenerator.GameModes;
[GameModeType(GameModeType.SpazerHunt)]
[Description("You thought they were worthless, but Samus needs to collect all of the spazers and use them to save the day. After all, who needs a Triforce when you have lots of firepower?")]
-public class SpazerHuntAltGameMode(IMetadataService metadata) : AltGameModeBase
+public class SpazerHuntGameMode(IMetadataService metadata) : GameModeBase
{
private TrackerBase _tracker = null!;
@@ -31,7 +33,7 @@ public override bool IsComplete(World world)
return GetCurrentCount(world) >= world.Config.GameModeOptions.SpazersRequired;
}
- public override void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState)
+ public override void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState, ParsedRomDetails? parsedRomDetails)
{
if (gameModeOptions.RandomizeNumericAmounts)
{
@@ -43,14 +45,15 @@ public override void UpdateInitialTrackerState(GameModeOptions gameModeOptions,
}
}
- public override void UpdateWorld(World world, int seed, GameModeOptions gameModeOptions)
+ public override void UpdateWorld(World world, Random rng, GameModeOptions gameModeOptions)
{
- var rng = new Random(seed);
- rng.Sanitize();
-
var swaps = new List();
var spazersInPool = gameModeOptions.SpazersInPool;
+
+ gameModeOptions.GanonCrystalCount = 7;
+ gameModeOptions.TourianBossCount = 4;
+
if (gameModeOptions.RandomizeNumericAmounts)
{
spazersInPool = rng.Next(gameModeOptions.MinSpazersInPool, gameModeOptions.MaxSpazersInPool + 1);
@@ -174,22 +177,22 @@ public override void InitializeTracker(TrackerBase tracker)
_tracker.ModeTracker.MultiplayerSyncReceived += ModeTrackerOnMultiplayerSyncReceived;
}
- public override AltGameModeInGameText? GetInGameText(World world)
+ public override GameModeInGameText GetInGameText(World world)
{
var spazersRequired = world.Config.GameModeOptions.SpazersRequired;
var spazersInPool = world.Config.GameModeOptions.SpazersInPool;
- return new AltGameModeInGameText
+ return new GameModeInGameText
{
- GanonIntro = metadata.AltGameModes.SpazerHuntGanonIntro?.Format(spazersRequired, spazersInPool),
+ GanonIntro = metadata.GameModes.SpazerHuntGanonIntro,
GanonIntroGoalsNotMet =
- metadata.AltGameModes.SpazerHuntGanonIntroGoalsNotMet?.Format(spazersRequired, spazersInPool),
- GanonGoalSign = metadata.AltGameModes.SpazerHuntGanonGoalSign?.Format(spazersRequired, spazersInPool),
+ metadata.GameModes.SpazerHuntGanonIntroGoalsNotMet?.Format(spazersRequired, spazersInPool),
+ GanonGoalSign = metadata.GameModes.SpazerHuntGanonGoalSign?.Format(spazersRequired, spazersInPool),
};
}
public override string? GetGameStartText(World world)
{
- return metadata.AltGameModes.SpazerHuntGameStarted?.ToString();
+ return metadata.GameModes.SpazerHuntGameStarted?.ToString();
}
public override bool IsKnowinglyComplete(World world)
@@ -213,11 +216,11 @@ public override bool OnViewingPyramidText(World world)
return allWorlds.SelectMany(w => w.Locations).Where(l => l.ItemType is ItemType.Spazer && l.Item.World.Id == world.Id).ToList();
}
- public override List? GetGoalUiDetails(World world)
+ public override List GetGoalUiDetails(World world, Progression progression)
{
if (world.State == null)
{
- return null;
+ return [];
}
var spazerCount = GetCurrentCount(world);
@@ -234,6 +237,11 @@ public override string GetSpoilerText(GameModeOptions gameModeOptions)
$"SpazersRequired = {gameModeOptions.SpazersRequired}, SpazersInPool = {gameModeOptions.SpazersInPool}";
}
+ public override Stream GetLiftOffOnGoalCompletionIpsPatch()
+ {
+ return IpsPatch.LiftOffOnGoalCompletionAlt();
+ }
+
private int GetCurrentCount(World world)
{
var allSpazerLocations = world.State?.LocationStates
@@ -250,10 +258,10 @@ private void LocationTrackerOnLocationCleared(object? sender, LocationClearedEve
if (IsComplete(_tracker.World))
{
- _tracker.AltGameModeService.MarkAltGameModeAsComplete();
+ _tracker.GameModeService.MarkGameModeAsComplete();
}
- _tracker.AltGameModeService.NotifyOfGoalStateChange();
+ _tracker.GameModeService.NotifyOfGoalStateChange();
}
private void ItemTrackerOnItemTracked(object? sender, ItemTrackedEventArgs e)
@@ -265,19 +273,19 @@ private void ItemTrackerOnItemTracked(object? sender, ItemTrackedEventArgs e)
if (IsComplete(_tracker.World))
{
- _tracker.AltGameModeService.MarkAltGameModeAsComplete();
+ _tracker.GameModeService.MarkGameModeAsComplete();
}
- _tracker.AltGameModeService.NotifyOfGoalStateChange();
+ _tracker.GameModeService.NotifyOfGoalStateChange();
}
private void ModeTrackerOnMultiplayerSyncReceived(object? sender, EventArgs e)
{
if (IsComplete(_tracker.World))
{
- _tracker.AltGameModeService.MarkAltGameModeAsComplete();
+ _tracker.GameModeService.MarkGameModeAsComplete();
}
- _tracker.AltGameModeService.NotifyOfGoalStateChange();
+ _tracker.GameModeService.NotifyOfGoalStateChange();
}
}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/VanillaGameMode.cs b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/VanillaGameMode.cs
new file mode 100644
index 00000000..b052baf4
--- /dev/null
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/GameModes/VanillaGameMode.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using TrackerCouncil.Smz3.Abstractions;
+using TrackerCouncil.Smz3.Data.GeneratedData;
+using TrackerCouncil.Smz3.Data.Options;
+using TrackerCouncil.Smz3.Data.ParsedRom;
+using TrackerCouncil.Smz3.Data.Services;
+using TrackerCouncil.Smz3.Data.ViewModels;
+using TrackerCouncil.Smz3.Data.WorldData;
+using TrackerCouncil.Smz3.SeedGenerator.FileData.IpsPatches;
+using TrackerCouncil.Smz3.Shared.Enums;
+using TrackerCouncil.Smz3.Shared.Models;
+
+namespace TrackerCouncil.Smz3.SeedGenerator.GameModes;
+
+[GameModeType(GameModeType.Vanilla)]
+[Description("Complete Zelda dungeons to obtain crystals to be able to defeat Ganon. Defeat bosses in Super Metroid to be able to enter Tourian and defeat Mother Brain.")]
+public class VanillaGameMode(IMetadataService metadata) : GameModeBase
+{
+ private TrackerBase _tracker = null!;
+
+ public override void InitializeTracker(TrackerBase tracker)
+ {
+ _tracker = tracker;
+ _tracker.RewardTracker.RewardsChanged += RewardTrackerOnRewardsChanged;
+ }
+
+ public override bool IsComplete(World world)
+ {
+ if (world.State == null)
+ {
+ return false;
+ }
+
+ var progression = _tracker.PlayerProgressionService.GetProgression(false);
+
+ return progression.CrystalCount >= world.Config.GameModeOptions.GanonCrystalCount &&
+ progression.MetroidBossCount >= world.Config.GameModeOptions.TourianBossCount;
+ }
+
+ public override void UpdateWorld(World world, Random rng, GameModeOptions gameModeOptions)
+ {
+ if (gameModeOptions.RandomizeNumericAmounts)
+ {
+ gameModeOptions.GanonCrystalCount = rng.Next(gameModeOptions.MinGanonCrystalCount, gameModeOptions.MaxGanonCrystalCount + 1);
+ gameModeOptions.TourianBossCount = rng.Next(gameModeOptions.MinTourianBossCount, gameModeOptions.MaxTourianBossCount + 1);
+ }
+ }
+
+ public override GameModeInGameText GetInGameText(World world)
+ {
+ var ganonCrystalCount = world.Config.GameModeOptions.GanonCrystalCount;
+ return new GameModeInGameText
+ {
+ GanonIntro = metadata.GameLines.GanonIntro,
+ GanonIntroGoalsNotMet = metadata.GameLines.GanonIntroGoalsNotMet,
+ GanonGoalSign = metadata.GameLines.GanonGoalSign?.Format(ganonCrystalCount == 1 ? "1 crystal" : $"{ganonCrystalCount} crystals"),
+ };
+ }
+
+ public override void UpdateInitialTrackerState(GameModeOptions gameModeOptions, TrackerState trackerState, ParsedRomDetails? parsedRomDetails)
+ {
+ int? markedGanonCrystalCount = null;
+ int? markedTourianBossCount = null;
+
+ if (parsedRomDetails == null)
+ {
+ if (gameModeOptions.RandomizeNumericAmounts)
+ {
+ markedGanonCrystalCount =
+ gameModeOptions.MinGanonCrystalCount == gameModeOptions.MaxGanonCrystalCount
+ ? gameModeOptions.MinGanonCrystalCount
+ : null;
+ markedTourianBossCount =
+ gameModeOptions.MinTourianBossCount == gameModeOptions.MaxTourianBossCount
+ ? gameModeOptions.MinTourianBossCount
+ : null;
+ }
+ else
+ {
+ markedGanonCrystalCount = gameModeOptions.GanonCrystalCount;
+ markedTourianBossCount = gameModeOptions.TourianBossCount;
+ }
+ }
+
+ trackerState.MarkedGanonCrystalCount = markedGanonCrystalCount;
+ trackerState.MarkedTourianBossCount = markedTourianBossCount;
+ }
+
+ public override List GetGoalUiDetails(World world, Progression progression)
+ {
+ var crystals = progression.CrystalCount;
+ var bosses = progression.MetroidBossCount;
+ var crystalsRequired = world.State?.MarkedGanonCrystalCount != null
+ ? $"{world.State?.MarkedGanonCrystalCount}"
+ : "?";
+ var bossesRequired = world.State?.MarkedTourianBossCount != null
+ ? $"{world.State?.MarkedTourianBossCount}"
+ : "?";
+ return
+ [
+ new GoalUiDetails { IconCategory = "Dungeons", Icon = "crystal.png", Text = $"{crystals}/{crystalsRequired}" },
+ new GoalUiDetails { IconCategory = "Dungeons", Icon = "metroid boss token.png", Text = $"{bosses}/{bossesRequired}" }
+ ];
+ }
+
+ public override string GetSpoilerText(GameModeOptions gameModeOptions)
+ {
+ return $"GanonCrystalCount = {gameModeOptions.GanonCrystalCount}, TourianBossCount = {gameModeOptions.TourianBossCount}";
+ }
+
+ public override Stream GetLiftOffOnGoalCompletionIpsPatch()
+ {
+ return IpsPatch.LiftOffOnGoalCompletionPrimary();
+ }
+
+ public override bool IsKnowinglyComplete(World world)
+ {
+ return world.State?.MarkedGanonCrystalCount == world.Config.GameModeOptions.GanonCrystalCount &&
+ world.State?.MarkedTourianBossCount == world.Config.GameModeOptions.TourianBossCount &&
+ IsComplete(world);
+ }
+
+ public override bool OnViewingPyramidText(World world)
+ {
+ if (world.State != null && world.State.MarkedGanonCrystalCount != world.Config.GameModeOptions.GanonCrystalCount)
+ {
+ _tracker.GameStateTracker.UpdateGanonRequirement(world.State?.GanonCrystalCount ?? world.Config.GameModeOptions.GanonCrystalCount, true);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void RewardTrackerOnRewardsChanged(object? sender, EventArgs e)
+ {
+ if (IsComplete(_tracker.World))
+ {
+ _tracker.GameModeService.MarkGameModeAsComplete();
+ }
+
+ _tracker.GameModeService.NotifyOfGoalStateChange();
+ }
+}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs
index 1e70d3c2..cb473c19 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs
@@ -10,8 +10,8 @@
using TrackerCouncil.Smz3.Data.Services;
using TrackerCouncil.Smz3.Data.WorldData;
using TrackerCouncil.Smz3.Data.WorldData.Regions;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.Contracts;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.SeedGenerator.Infrastructure;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Shared.Models;
@@ -27,7 +27,7 @@ public class GameHintService(
IMetadataService metadataService,
ILogger logger,
PlaythroughService playthroughService,
- AltGameModeFactory altgameModeFactory)
+ GameModeFactory gameModeFactory)
: IGameHintService
{
private readonly GameLinesConfig _gameLines = metadataService.GameLines;
@@ -66,7 +66,7 @@ public void GetInGameHints(World hintPlayerWorld, List allWorlds, Playthr
}
_random = new Random(seed).Sanitize();
- var goalLocations = allWorlds.SelectMany(x => altgameModeFactory.GetGameModeLocations(x, allWorlds) ?? []).ToList();
+ var goalLocations = allWorlds.SelectMany(x => gameModeFactory.GetGameModeLocations(x, allWorlds) ?? []).ToList();
var allHints = new List();
@@ -131,7 +131,7 @@ public void GetInGameHints(World hintPlayerWorld, List allWorlds, Playthr
return null;
}
- goalLocations ??= allWorlds.SelectMany(x => altgameModeFactory.GetGameModeLocations(x, allWorlds) ?? []).ToList();
+ goalLocations ??= allWorlds.SelectMany(x => gameModeFactory.GetGameModeLocations(x, allWorlds) ?? []).ToList();
var locationUsefulness = new ConcurrentBag<(Location Location, LocationUsefulness Usefulness)>();
@@ -148,7 +148,7 @@ public void GetInGameHints(World hintPlayerWorld, List allWorlds, Playthr
public LocationUsefulness GetUsefulness(List locations, List allWorlds, Reward? ignoredReward, List? goalLocations = null)
{
- goalLocations ??= allWorlds.SelectMany(x => altgameModeFactory.GetGameModeLocations(x, allWorlds) ?? []).ToList();
+ goalLocations ??= allWorlds.SelectMany(x => gameModeFactory.GetGameModeLocations(x, allWorlds) ?? []).ToList();
return CheckIfLocationsAreImportant(allWorlds, locations, goalLocations, ignoredReward);
}
@@ -458,6 +458,11 @@ private LocationUsefulness VerifyWorld(Playthrough.Sphere sphere, World world, L
{
return LocationUsefulness.Mandatory;
}
+ // If all dungeons game mode, make sure the player can get the required crystal counts
+ if (world.Config.GameModeOptions.SelectedGameModeType == GameModeType.AllDungeons && (!progression.AllCrystals || !progression.AllPendants || progression.MetroidBossCount < 4 || !worldLocations.ContainsKey(LocationId.GanonsTowerMoldormChest)))
+ {
+ return LocationUsefulness.Mandatory;
+ }
// If it's a different game mode, return that it's goal blocking if it's either a goal location or prevents access to a goal location
else if (world.Config.GameModeOptions.SelectedGameModeType != GameModeType.Vanilla && goalLocations?.Any(x =>
!x.IsAvailable(progression) || checkedLocations.Any(y => y.Id == x.Id && y.World == x.World)) == true)
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs
index c5ff78a6..9b05a337 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs
@@ -15,9 +15,9 @@
using TrackerCouncil.Smz3.Data.Options;
using TrackerCouncil.Smz3.Data.ParsedRom;
using TrackerCouncil.Smz3.Data.Services;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.FileData;
using TrackerCouncil.Smz3.SeedGenerator.FileData.IpsPatches;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Shared.Models;
@@ -35,7 +35,7 @@ public class RomGenerationService(
IMsuSelectorService? msuSelectorService,
IMsuTypeService? msuTypeService,
RomTextService romTextService,
- AltGameModeFactory altGameModeFactory)
+ GameModeFactory gameModeFactory)
: IRomGenerationService
{
private readonly ILogger _logger = logger;
@@ -174,9 +174,9 @@ public byte[] GenerateRomBytes(RandomizerOptions options, SeedData? seed, Parsed
if (seed is { WorldGenerationData.LocalWorld.Config.GameModeOptions.LiftOffOnGoalCompletion: true })
{
- using var goalPatch = seed.WorldGenerationData.LocalWorld.Config.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla
- ? IpsPatch.LiftOffOnGoalCompletionPrimary()
- : IpsPatch.LiftOffOnGoalCompletionAlt();
+ using var goalPatch =
+ gameModeFactory.GetLiftOffOnGoalCompletionIpsPatch(seed.WorldGenerationData.LocalWorld.Config
+ .GameModeOptions);
Rom.ApplySuperMetroidIps(rom, goalPatch);
}
@@ -392,7 +392,7 @@ public async Task GeneratePreSeededRomAsync(RandomizerOption
romTextService.PrepareAutoTrackerFiles(options);
- var rom = await SaveSeedToDatabaseAsync(options, seed, romPath, spoilerPath, multiplayerGameDetails);
+ var rom = await SaveSeedToDatabaseAsync(options, seed, romPath, spoilerPath, multiplayerGameDetails, parsedRomDetails);
try
{
@@ -415,7 +415,7 @@ public async Task GeneratePreSeededRomAsync(RandomizerOption
/// The path to the spoiler file
/// Details of the connected multiplayer game
/// The db entry for the generated rom
- private async Task SaveSeedToDatabaseAsync(RandomizerOptions options, SeedData seed, string romPath, string spoilerPath, MultiplayerGameDetails? multiplayerGameDetails)
+ private async Task SaveSeedToDatabaseAsync(RandomizerOptions options, SeedData seed, string romPath, string spoilerPath, MultiplayerGameDetails? multiplayerGameDetails, ParsedRomDetails? parsedRomDetails)
{
var settingsString = seed.Configs.Count() > 1
? Config.ToConfigString(seed.Configs)
@@ -445,7 +445,7 @@ private async Task SaveSeedToDatabaseAsync(RandomizerOptions optio
if (rom.TrackerState != null)
{
- altGameModeFactory.UpdateInitialTrackerState(seed.WorldGenerationData.LocalWorld.Config.GameModeOptions, rom.TrackerState);
+ gameModeFactory.UpdateInitialTrackerState(seed.WorldGenerationData.LocalWorld.Config.GameModeOptions, rom.TrackerState, parsedRomDetails);
await dbContext.SaveChangesAsync();
}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs
index db20a629..3917d362 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs
@@ -10,14 +10,14 @@
using TrackerCouncil.Smz3.Data.GeneratedData;
using TrackerCouncil.Smz3.Data.Options;
using TrackerCouncil.Smz3.Data.WorldData.Regions;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.Contracts;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.Shared;
using TrackerCouncil.Smz3.Shared.Enums;
namespace TrackerCouncil.Smz3.SeedGenerator.Generation;
-public class RomTextService(ILogger logger, IGameHintService gameHintService, ISnesConnectorService snesConnectorService, AltGameModeFactory altGameModeFactory)
+public class RomTextService(ILogger logger, IGameHintService gameHintService, ISnesConnectorService snesConnectorService, GameModeFactory gameModeFactory)
{
private static readonly string s_plandoSchemaPath = @"https://raw.githubusercontent.com/TheTrackerCouncil/SMZ3CasConfigs/refs/heads/main/Schemas/plando.json";
@@ -197,7 +197,7 @@ private string GetSpoilerLog(RandomizerOptions options, SeedData seed, bool conf
if (!isParsedRom)
{
- log.AppendLine(altGameModeFactory.GetSpoilerText(gameModeOptions));
+ log.AppendLine(gameModeFactory.GetSpoilerText(gameModeOptions));
}
string keysanityOptions;
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs
index 1f2b3da6..bbd345f9 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs
@@ -9,9 +9,9 @@
using TrackerCouncil.Smz3.Data.Options;
using TrackerCouncil.Smz3.Data.WorldData;
using TrackerCouncil.Smz3.Data.WorldData.Regions;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.Contracts;
using TrackerCouncil.Smz3.SeedGenerator.FileData;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.SeedGenerator.Infrastructure;
using TrackerCouncil.Smz3.Shared.Enums;
@@ -24,7 +24,7 @@ public class Smz3Randomizer(
ILogger logger,
IPatcherService patcherService,
PlaythroughService playthroughService,
- AltGameModeFactory altGameModeFactory)
+ GameModeFactory gameModeFactory)
: ISeededRandomizer
{
public static string Name => "Super Metroid & A Link to the Past Cas’ Randomizer";
@@ -54,7 +54,7 @@ public SeedData GenerateSeed(List configs, string? seed, CancellationTok
var worlds = new List();
if (primaryConfig.SingleWorld)
{
- worlds.Add(altGameModeFactory.UpdateWorld(new World(primaryConfig, "Player", 0,
+ worlds.Add(gameModeFactory.UpdateWorld(new World(primaryConfig, "Player", 0,
Guid.NewGuid().ToString("N")), seedNumber));
logger.LogDebug(
"Seed: {SeedNumber} | Race: {PrimaryConfigRace} | Keysanity: {PrimaryConfigKeysanityMode} | Item placement: {PrimaryConfigItemPlacementRule}",
@@ -63,7 +63,7 @@ public SeedData GenerateSeed(List configs, string? seed, CancellationTok
else
{
worlds.AddRange(configs.OrderBy(x => x.Id).Select(config =>
- altGameModeFactory.UpdateWorld(new World(config, config.PlayerName, config.Id, config.PlayerGuid, config.IsLocalConfig), rng.Next())));
+ gameModeFactory.UpdateWorld(new World(config, config.PlayerName, config.Id, config.PlayerGuid, config.IsLocalConfig), rng.Next())));
logger.LogDebug(
"Seed: {SeedNumber} | Race: {PrimaryConfigRace} | World Count: {Count}",
seedNumber, primaryConfig.Race, configs.Count);
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3RomParser.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3RomParser.cs
index 63b3f2fc..5ed6a681 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3RomParser.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3RomParser.cs
@@ -121,6 +121,7 @@ public ParsedRomDetails ParseRomFile(string filePath)
var ganonCrystalCount = GoalsPatch.GetGanonCrystalCountFromRom(rom);
var tourianBossCount = GoalsPatch.GetTourianBossCountFromRom(rom, romGenerator == RomGenerator.Cas);
var openPyramid = GoalsPatch.GetOpenPyramid(rom);
+ var gameModeType = GoalsPatch.GetGameModeType(rom);
var text = ZeldaTextsPatch.ParseRomText(rom);
var spinJumpAnimations =
romGenerator != RomGenerator.Cas && MetadataPatch.GetSpinJumpAnimationsFromRom(rom);
@@ -149,7 +150,8 @@ public ParsedRomDetails ParseRomFile(string filePath)
Prerequisites = prerequisites,
StartingItems = startingItems,
ParsedText = text,
- SpinJumpAnimations = spinJumpAnimations
+ SpinJumpAnimations = spinJumpAnimations,
+ GameModeType = gameModeType
};
}
catch (Exception e)
@@ -174,6 +176,7 @@ public SeedData GenerateSeedData(RandomizerOptions options, ParsedRomDetails par
config.GameModeOptions.TourianBossCount = parsedRomDetails.TourianBossCount;
config.GameModeOptions.OpenPyramid = parsedRomDetails.OpenPyarmid;
config.GameModeOptions.SkipTourianBossDoor = parsedRomDetails.SkipTourianBossDoor;
+ config.GameModeOptions.SelectedGameModeType = parsedRomDetails.GameModeType;
config.GameModeOptions.ShuffleMetroidBossTokens = true;
config.LocationItems.Clear();
config.ItemOptions = parsedRomDetails.StartingItems.ToDictionary(x => $"ItemType:{x.Key}", x => x.Value);
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs
index 45f115ec..56051966 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs
@@ -3,9 +3,9 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using TrackerCouncil.Smz3.Abstractions;
using TrackerCouncil.Smz3.Data.Interfaces;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.Contracts;
using TrackerCouncil.Smz3.SeedGenerator.FileData.Patches;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.SeedGenerator.Generation;
using TrackerCouncil.Smz3.SeedGenerator.Infrastructure;
using TrackerCouncil.Smz3.Shared.Models;
@@ -17,7 +17,7 @@ public static class RandomizerServiceCollectionExtensions
public static IServiceCollection AddRandomizerServices(this IServiceCollection services)
{
services.AddRomPatches();
- services.AddGameModes();
+ services.AddGameModes();
services.AddSingleton();
services.AddGeneratedRomLoader();
services.AddSmz3Randomizer();
@@ -88,15 +88,15 @@ private static IServiceCollection AddRomPatches(this IServiceCollecti
private static IServiceCollection AddGameModes(this IServiceCollection services)
{
var moduleTypes = typeof(TAssembly).Assembly.GetTypes()
- .Where(x => x.IsSubclassOf(typeof(AltGameModeBase)));
+ .Where(x => x.IsSubclassOf(typeof(GameModeBase)));
foreach (var moduleType in moduleTypes)
{
services.TryAddScoped(moduleType);
- AltGameModeFactory.AddGameModeClass(moduleType);
+ GameModeFactory.AddGameModeClass(moduleType);
}
- services.AddSingleton();
+ services.AddSingleton();
return services;
}
diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj b/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj
index c6bf4a41..9bdf743d 100644
--- a/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj
+++ b/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj
@@ -49,6 +49,7 @@
+
diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/GameModeType.cs b/src/TrackerCouncil.Smz3.Shared/Enums/GameModeType.cs
index 7068968c..2b7a6211 100644
--- a/src/TrackerCouncil.Smz3.Shared/Enums/GameModeType.cs
+++ b/src/TrackerCouncil.Smz3.Shared/Enums/GameModeType.cs
@@ -7,6 +7,9 @@ public enum GameModeType
[Description("Collect crystals and defeat Metroid bosses")]
Vanilla,
+ [Description("All dungeons and defeat Metroid bosses")]
+ AllDungeons,
+
[Description("Spazer Hunt (auto tracking required)")]
SpazerHunt
}
diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs
index 77cfb329..ad6a71fa 100644
--- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs
@@ -10,13 +10,13 @@
using SNI;
using TrackerCouncil.Smz3.Abstractions;
using TrackerCouncil.Smz3.Data.Tracking;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Tracking.Services;
namespace TrackerCouncil.Smz3.Tracking.AutoTracking.AutoTrackerModules;
-public class GameMonitor(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldQueryService worldQueryService, AltGameModeFactory altGameModeFactory) : AutoTrackerModule(tracker, snesConnector, logger)
+public class GameMonitor(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldQueryService worldQueryService, GameModeFactory gameModeFactory) : AutoTrackerModule(tracker, snesConnector, logger)
{
private bool bIsCheckingGameStart;
@@ -52,7 +52,7 @@ public override void Initialize()
});
// If the player is using an alt game mode, make sure the flag is set
- if (worldQueryService.World.Config.GameModeOptions.SelectedGameModeType != GameModeType.Vanilla)
+ if (worldQueryService.World.Config.GameModeOptions.IsAltGameMode())
{
SnesConnector.AddRecurringMemoryRequest(new SnesRecurringMemoryRequest()
{
@@ -64,7 +64,7 @@ public override void Initialize()
Length = 1,
OnResponse = CheckAltGameModeFlag,
FrequencySeconds = 15,
- Filter = () => HasValidState && Tracker.AltGameModeService.IsAltGameModeComplete
+ Filter = () => HasValidState && Tracker.GameModeService.IsGameModeComplete
});
}
}
@@ -165,7 +165,7 @@ private void MarkAsStarted()
if (bIsFirstStart)
{
- var gameModeString = altGameModeFactory.GetGameStartText(Tracker.World);
+ var gameModeString = gameModeFactory.GetGameStartText(Tracker.World);
if (!string.IsNullOrEmpty(gameModeString))
{
Tracker.Say(text: gameModeString);
diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs
index 12e777d4..bbc82ba4 100644
--- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs
@@ -20,7 +20,7 @@ public override void Initialize()
Address = 0x7e0FB2,
Length = 0x2,
OnResponse = CheckShip,
- Filter = () => IsInMetroid && (AutoTracker.HasDefeatedBothBosses || (Tracker.GameStateTracker.HasFinishedGoal && Tracker.LocalConfig?.GameModeOptions.LiftOffOnGoalCompletion == true))
+ Filter = () => IsInMetroid && (AutoTracker.HasDefeatedBothBosses || (Tracker.GameModeService.IsGameModeComplete && Tracker.LocalConfig?.GameModeOptions.LiftOffOnGoalCompletion == true))
});
}
diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs
index bf9eb3e9..3699136e 100644
--- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs
@@ -102,6 +102,13 @@ private void CheckZeldaMisc(SnesData data, SnesData? prevData)
AutoTracker.PlayerHasFairy = hasFairy;
+ var openPyramid = data.CheckUInt8Flag(0x5B, 0x20);
+ if (openPyramid != AutoTracker.OpenPyramid)
+ {
+ AutoTracker.OpenPyramid = openPyramid;
+ AutoTracker.OnOpenPyramidChanged();
+ }
+
// Activated flute
if (data.CheckUInt8Flag(0x10C, 0x01) && !prevData.CheckUInt8Flag(0x10C, 0x01))
{
diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs
index 8e825c65..2762b0cc 100644
--- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs
@@ -80,15 +80,7 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerZeldaState currentState
}
else if (currentState.OverworldScreen == 91 && currentState.IsWithinRegion(1961, 1784, 1976, 1785))
{
- if (tracker.LocalConfig?.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla &&
- World.State?.GanonCrystalCount != World.State?.MarkedGanonCrystalCount)
- {
- tracker.GameStateTracker.UpdateGanonRequirement(World.State?.GanonCrystalCount ?? World.Config.GameModeOptions.GanonCrystalCount, true);
- }
- else if (tracker.AltGameModeService.HasAltGameMode)
- {
- tracker.AltGameModeService.OnViewingPyramidText();
- }
+ tracker.GameModeService.OnViewingPyramidText();
}
// Viewed Bombos tablet text
else if (currentState is { OverworldScreen: 48, LinkState: 0 } &&
diff --git a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs
index d6d7757d..4488c89a 100644
--- a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs
@@ -731,8 +731,8 @@ public override void UpdateAllAccessibility(bool forceRefreshAll, params Item[]
var actualProgression = PlayerProgressionService.GetProgression(false);
var assumedKeysProgression = PlayerProgressionService.GetProgression(true);
- var hasAltGameMode = AltGameModeService.HasAltGameMode;
- var isAltGameModeComplete = hasAltGameMode && AltGameModeService.IsAltGameModeKnowinglyComplete();
+ var hasAltGameMode = GameModeService.HasAltGameMode;
+ var isAltGameModeComplete = hasAltGameMode && GameModeService.IsGameModeKnowinglyComplete();
World.DarkWorldNorthEast.UpdateGanonAccessibility(actualProgression, assumedKeysProgression, hasAltGameMode, isAltGameModeComplete);
World.WestCrateria.UpdateMotherBrainAccessibility(actualProgression, hasAltGameMode, isAltGameModeComplete);
diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerAltGameModeService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerAltGameModeService.cs
deleted file mode 100644
index 5982fa0a..00000000
--- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerAltGameModeService.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System;
-using System.Collections.Generic;
-using TrackerCouncil.Smz3.Abstractions;
-using TrackerCouncil.Smz3.Data.Services;
-using TrackerCouncil.Smz3.Data.ViewModels;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
-using TrackerCouncil.Smz3.SeedGenerator.Contracts;
-using TrackerCouncil.Smz3.Shared.Enums;
-
-namespace TrackerCouncil.Smz3.Tracking.TrackingServices;
-
-internal class TrackerAltGameModeService(AltGameModeFactory altGameModeFactory, IWorldAccessor worldAccessor) : TrackerService, ITrackerAltGameModeService
-{
- private GameModeType _gameModeType = GameModeType.Vanilla;
- private AltGameModeBase? _gameMode;
-
- public GameModeType GameModeType => _gameModeType;
- public bool HasAltGameMode => _gameModeType != GameModeType.Vanilla;
- public bool IsAltGameModeComplete { get; private set; }
- public event EventHandler? GoalStateChanged;
-
- public void MarkAltGameModeAsComplete()
- {
- IsAltGameModeComplete = true;
- Tracker.GameStateTracker.HasFinishedGoal = true;
- Tracker.GameService?.TrySetGameModeComplete();
- Tracker.UpdateAllAccessibility(false);
- }
-
- public bool IsAltGameModeKnowinglyComplete()
- {
- return HasAltGameMode && _gameMode!.IsKnowinglyComplete(Tracker.World);
- }
-
- public void OnViewingPyramidText()
- {
- if (!HasAltGameMode)
- {
- return;
- }
-
- if (_gameMode!.OnViewingPyramidText(Tracker.World))
- {
- _ = Tracker.SaveAsync();
- Tracker.UpdateAllAccessibility(false);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
- }
- }
-
- public void NotifyOfGoalStateChange()
- {
- GoalStateChanged?.Invoke(this, EventArgs.Empty);
- }
-
- public List GetGoalUiDetails()
- {
- var state = _gameMode?.GetGoalUiDetails(Tracker.World);
- if (state?.Count > 0) return state;
- var progression = Tracker.PlayerProgressionService.GetProgression(false);
- var crystals = progression.CrystalCount;
- var bosses = progression.MetroidBossCount;
- var crystalsRequired = Tracker.World.State?.MarkedGanonCrystalCount != null
- ? $"{Tracker.World.State?.MarkedGanonCrystalCount}"
- : "?";
- var bossesRequired = Tracker.World.State?.MarkedTourianBossCount != null
- ? $"{Tracker.World.State?.MarkedTourianBossCount}"
- : "?";
- return
- [
- new GoalUiDetails { IconCategory = "Dungeons", Icon = "crystal.png", Text = $"{crystals}/{crystalsRequired}" },
- new GoalUiDetails { IconCategory = "Dungeons", Icon = "metroid boss token.png", Text = $"{bosses}/{bossesRequired}" }
- ];
- }
-
- public override void PostInitialize()
- {
- _gameModeType = worldAccessor.World.Config.GameModeOptions.SelectedGameModeType;
- if (_gameModeType == GameModeType.Vanilla)
- {
- return;
- }
- _gameMode = altGameModeFactory.GetGameMode(_gameModeType);
-
- IsAltGameModeComplete = _gameMode.IsComplete(worldAccessor.World);
- Tracker.GameStateTracker.HasFinishedGoal = IsAltGameModeComplete;
-
- _gameMode.InitializeTracker(Tracker);
- }
-}
diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerBossService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerBossService.cs
index aea017e1..4cdc7c1c 100644
--- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerBossService.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerBossService.cs
@@ -111,6 +111,7 @@ public void MarkBossAsDefeated(Boss boss, bool admittedGuilt = true, float? conf
if (boss.Region != null)
{
MarkBossAsDefeated(boss.Region, confidence, autoTracked, admittedGuilt, force: force);
+ UpdateAllAccessibility(false);
return;
}
diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameModeService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameModeService.cs
new file mode 100644
index 00000000..0696b48a
--- /dev/null
+++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameModeService.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using TrackerCouncil.Smz3.Abstractions;
+using TrackerCouncil.Smz3.Data.ViewModels;
+using TrackerCouncil.Smz3.SeedGenerator.Contracts;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
+using TrackerCouncil.Smz3.Shared.Enums;
+
+namespace TrackerCouncil.Smz3.Tracking.TrackingServices;
+
+internal class TrackerGameModeService(GameModeFactory gameModeFactory, IWorldAccessor worldAccessor) : TrackerService, ITrackerGameModeService
+{
+ private GameModeType _gameModeType = GameModeType.Vanilla;
+ private GameModeBase? _gameMode;
+
+ public GameModeType GameModeType => _gameModeType;
+ public bool HasAltGameMode => _gameModeType != GameModeType.Vanilla && _gameModeType != GameModeType.AllDungeons;
+ public bool IsGameModeComplete { get; private set; }
+ public event EventHandler? GoalStateChanged;
+
+ public void MarkGameModeAsComplete()
+ {
+ IsGameModeComplete = true;
+ Tracker.GameService?.TrySetGameModeComplete();
+ Tracker.UpdateAllAccessibility(false);
+ }
+
+ public bool IsGameModeKnowinglyComplete()
+ {
+ return _gameMode!.IsKnowinglyComplete(Tracker.World);
+ }
+
+ public void OnViewingPyramidText()
+ {
+ if (_gameMode!.OnViewingPyramidText(Tracker.World))
+ {
+ _ = Tracker.SaveAsync();
+ Tracker.UpdateAllAccessibility(false);
+ Tracker.GameModeService.NotifyOfGoalStateChange();
+ }
+ }
+
+ public void NotifyOfGoalStateChange()
+ {
+ GoalStateChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ public List GetGoalUiDetails()
+ {
+ var progression = Tracker.PlayerProgressionService.GetProgression(false);
+ return _gameMode?.GetGoalUiDetails(Tracker.World, progression) ?? [];
+ }
+
+ public override void PostInitialize()
+ {
+ _gameModeType = worldAccessor.World.Config.GameModeOptions.SelectedGameModeType;
+ _gameMode = gameModeFactory.GetGameMode(_gameModeType);
+ _gameMode.InitializeTracker(Tracker);
+ IsGameModeComplete = _gameMode.IsComplete(worldAccessor.World);
+ NotifyOfGoalStateChange();
+ }
+}
diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs
index d6b9f516..ee1b57fa 100644
--- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs
@@ -8,16 +8,14 @@
using TrackerCouncil.Smz3.Data.Tracking;
using TrackerCouncil.Smz3.Data.WorldData;
using TrackerCouncil.Smz3.Data.WorldData.Regions;
-using TrackerCouncil.Smz3.SeedGenerator.Contracts;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Shared.Models;
namespace TrackerCouncil.Smz3.Tracking.TrackingServices;
-internal class TrackerGameStateService(IMetadataService metadataService, IWorldAccessor worldAccessor) : TrackerService, ITrackerGameStateService
+internal class TrackerGameStateService(IMetadataService metadataService) : TrackerService, ITrackerGameStateService
{
public bool HasBeatenGame { get; protected set; }
- public bool HasFinishedGoal { get; set; }
public ViewedObject? LastViewedObject { get; set; }
public Region? CurrentRegion { get; protected set; }
public string CurrentMap { get; protected set; } = "";
@@ -323,14 +321,12 @@ public void UpdateGanonRequirement(int crystalAmount, bool autoTracked)
World.State.MarkedGanonCrystalCount = crystalAmount;
World.UpdateLegacyWorld(World.State);
UpdateAllAccessibility(true);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
Tracker.Say(x => x.UpdatedGanonCrystalRequirement, args: [crystalAmount]);
AddUndo(autoTracked, () =>
{
World.State.MarkedGanonCrystalCount = previousAmount;
World.UpdateLegacyWorld(World.State);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
});
}
@@ -345,7 +341,6 @@ public void UpdateTourianRequirement(int bossAmount, bool autoTracked)
World.State.MarkedTourianBossCount = bossAmount;
World.UpdateLegacyWorld(World.State);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
UpdateAllAccessibility(true);
Tracker.Say(x => x.UpdatedTourianBossRequirement, args: [bossAmount]);
@@ -353,7 +348,6 @@ public void UpdateTourianRequirement(int bossAmount, bool autoTracked)
{
World.State.MarkedTourianBossCount = previousAmount;
World.UpdateLegacyWorld(World.State);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
});
}
@@ -402,18 +396,4 @@ public void UpdateTalkingToZora(bool isTalking, AutoTrackerZeldaState? zeldaStat
IsTalkingToZora = false;
}
}
-
- public override void PostInitialize()
- {
- base.PostInitialize();
- if (worldAccessor.World.Config.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
- {
- var progression = Tracker.PlayerProgressionService.GetProgression(true);
- if (progression.CrystalCount >= Tracker.LocalConfig?.GameModeOptions.GanonCrystalCount &&
- progression.MetroidBossCount >= Tracker.LocalConfig?.GameModeOptions.TourianBossCount)
- {
- Tracker.GameStateTracker.HasFinishedGoal = true;
- }
- }
- }
}
diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerRewardService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerRewardService.cs
index 6f7a243f..44c9cde7 100644
--- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerRewardService.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerRewardService.cs
@@ -3,18 +3,18 @@
using System.Linq;
using Microsoft.Extensions.Logging;
using TrackerCouncil.Smz3.Abstractions;
-using TrackerCouncil.Smz3.Data.Options;
using TrackerCouncil.Smz3.Data.Services;
using TrackerCouncil.Smz3.Data.WorldData;
using TrackerCouncil.Smz3.Data.WorldData.Regions;
using TrackerCouncil.Smz3.Shared;
using TrackerCouncil.Smz3.Shared.Enums;
-using TrackerCouncil.Smz3.Tracking.Services;
namespace TrackerCouncil.Smz3.Tracking.TrackingServices;
internal class TrackerRewardService(ILogger logger, IPlayerProgressionService playerProgressionService, IMetadataService metadataService) : TrackerService, ITrackerRewardService
{
+ public event EventHandler? RewardsChanged;
+
public void SetAreaReward(IHasReward rewardRegion, RewardType? reward = null, float? confidence = null, bool autoTracked = false)
{
var originalReward = rewardRegion.MarkedReward;
@@ -41,13 +41,13 @@ public void SetAreaReward(IHasReward rewardRegion, RewardType? reward = null, fl
}
UpdateAllAccessibility(false);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
AddUndo(autoTracked, () =>
{
rewardRegion.MarkedReward = originalReward;
UpdateAllAccessibility(false);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
});
}
@@ -75,18 +75,7 @@ public void GiveAreaReward(IHasReward rewardRegion, bool isAutoTracked, bool sta
}
UpdateAllAccessibility(false);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
-
- // Check if the player has defeated all bosses and gotten all crystals
- if (config.GameModeOptions.SelectedGameModeType == GameModeType.Vanilla)
- {
- var progression = Tracker.PlayerProgressionService.GetProgression(true);
- if (progression.CrystalCount >= config.GameModeOptions.GanonCrystalCount &&
- progression.MetroidBossCount >= config.GameModeOptions.TourianBossCount)
- {
- Tracker.GameStateTracker.HasFinishedGoal = true;
- }
- }
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
// TODO: Add a response
@@ -95,7 +84,7 @@ public void GiveAreaReward(IHasReward rewardRegion, bool isAutoTracked, bool sta
rewardRegion.HasReceivedReward = false;
rewardRegion.MarkedReward = previousMarkedReward;
UpdateAllAccessibility(true);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
});
}
@@ -109,7 +98,7 @@ public void RemoveAreaReward(IHasReward rewardRegion, bool stateResponse)
rewardRegion.HasReceivedReward = false;
UpdateAllAccessibility(true);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
// TODO: Add a response
@@ -117,7 +106,7 @@ public void RemoveAreaReward(IHasReward rewardRegion, bool stateResponse)
{
rewardRegion.HasReceivedReward = false;
UpdateAllAccessibility(false);
- Tracker.AltGameModeService.NotifyOfGoalStateChange();
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
});
}
@@ -131,9 +120,11 @@ public void SetUnmarkedRewards(RewardType reward, float? confidence = null)
{
Tracker.Say(response: Responses.RemainingDungeonsMarked, args: [metadataService.GetName(reward)]);
unmarkedRegions.ForEach(dungeon => dungeon.MarkedReward = reward);
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
AddUndo(() =>
{
unmarkedRegions.ForEach(dungeon => dungeon.MarkedReward = RewardType.None);
+ RewardsChanged?.Invoke(this, EventArgs.Empty);
});
}
else
diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs
index 86d8a7e0..dbaa22f1 100644
--- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs
+++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs
@@ -76,12 +76,14 @@ public override void AddCommands()
{
var count = int.Parse(result.Semantics[ItemCountKey].Value);
TrackerBase.GameStateTracker.UpdateGanonRequirement(count, false);
+ TrackerBase.GameModeService.NotifyOfGoalStateChange();
});
AddCommand("Tourian Boss Count", GetTourianBossCountRule(), (result) =>
{
var count = int.Parse(result.Semantics[ItemCountKey].Value);
TrackerBase.GameStateTracker.UpdateTourianRequirement(count, false);
+ TrackerBase.GameModeService.NotifyOfGoalStateChange();
});
}
}
diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs
index 60546563..387e5417 100644
--- a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs
+++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs
@@ -238,19 +238,19 @@ public void StartTracker()
tracker.AutoTracker.AutoTrackerConnected += AutoTrackerOnAutoTrackerEnabled;
tracker.AutoTracker.AutoTrackerDisconnected += AutoTrackerOnAutoTrackerEnabled;
tracker.AutoTracker.AutoTrackerConnectorChanged += AutoTrackerOnAutoTrackerConnectorChanged;
- tracker.AltGameModeService.GoalStateChanged += AltGameModeServiceOnGoalStateChanged;
+ tracker.GameModeService.GoalStateChanged += GameModeServiceOnGoalStateChanged;
tracker.AutoTracker.SetConnector(Options.GeneralOptions.SnesConnectorSettings);
}
- private void AltGameModeServiceOnGoalStateChanged(object? sender, EventArgs e)
+ private void GameModeServiceOnGoalStateChanged(object? sender, EventArgs e)
{
UpdateGoals();
}
private void UpdateGoals()
{
- _model.Goals = tracker.AltGameModeService.GetGoalUiDetails().Select(x => new GoalUiDetails
+ _model.Goals = tracker.GameModeService.GetGoalUiDetails().Select(x => new GoalUiDetails
{
Icon = uiService.GetSpritePath(x.IconCategory, x.Icon, out _) ?? "",
Text = x.Text
diff --git a/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsBasicPanel.axaml b/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsBasicPanel.axaml
index 9fec8cb1..88e7338a 100644
--- a/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsBasicPanel.axaml
+++ b/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsBasicPanel.axaml
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:AvaloniaControls.Controls;assembly=AvaloniaControls"
xmlns:viewModels1="clr-namespace:TrackerCouncil.Smz3.Data.ViewModels;assembly=TrackerCouncil.Smz3.Data"
- mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="TrackerCouncil.Smz3.UI.Views.GenerationSettingsBasicPanel"
x:DataType="viewModels1:GenerationWindowViewModel"
Loaded="Control_OnLoaded">
@@ -139,7 +139,6 @@
IsVisible="{Binding Basic.IsShuffleStyleVisible}" />
-
diff --git a/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml b/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml
index 0d8278b6..bf4030e8 100644
--- a/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml
+++ b/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml
@@ -30,7 +30,16 @@
-
+
+
+
+
+
+ Skip Ganon and Tourian by lifting off from the ship after completing the goal
+
+
+
+
diff --git a/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml.cs
index b215636d..1d2881c7 100644
--- a/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml.cs
+++ b/src/TrackerCouncil.Smz3.UI/Views/GenerationSettingsWindow.axaml.cs
@@ -10,7 +10,7 @@
using TrackerCouncil.Smz3.Data.ParsedRom;
using TrackerCouncil.Smz3.Data.Services;
using TrackerCouncil.Smz3.Data.ViewModels;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.Shared.Enums;
using TrackerCouncil.Smz3.Shared.Models;
using Dispatcher = Avalonia.Threading.Dispatcher;
@@ -29,12 +29,12 @@ public GenerationSettingsWindow()
DataContext = new GenerationWindowViewModel();
}
- public GenerationSettingsWindow(GenerationSettingsWindowService generationSettingsWindowService, IServiceProvider serviceProvider, AltGameModeFactory altGameModeFactory)
+ public GenerationSettingsWindow(GenerationSettingsWindowService generationSettingsWindowService, IServiceProvider serviceProvider, GameModeFactory gameModeFactory)
{
_generationSettingsWindowService = generationSettingsWindowService;
_serviceProvider = serviceProvider;
InitializeComponent();
- DataContext = BasicPanel.Data = _model = _generationSettingsWindowService.GetViewModel(altGameModeFactory.GetGameModeDescriptions());
+ DataContext = BasicPanel.Data = _model = _generationSettingsWindowService.GetViewModel(gameModeFactory.GetGameModeDescriptions());
BasicPanel.SetServices(serviceProvider, generationSettingsWindowService);
}
diff --git a/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs b/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs
index 0e1959f3..02ba826e 100644
--- a/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs
+++ b/tests/TrackerCouncil.Smz3.Tests/LogicTests/RandomizerTests.cs
@@ -11,12 +11,13 @@
using TrackerCouncil.Smz3.Data.Options;
using TrackerCouncil.Smz3.Data.Services;
using TrackerCouncil.Smz3.Data.WorldData;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.Contracts;
using TrackerCouncil.Smz3.SeedGenerator.FileData.Patches;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.SeedGenerator.Generation;
using TrackerCouncil.Smz3.SeedGenerator.Infrastructure;
using TrackerCouncil.Smz3.Shared.Enums;
+using TrackerCouncil.Smz3.Tests.Text;
using Xunit;
namespace TrackerCouncil.Smz3.Tests.LogicTests;
@@ -175,8 +176,9 @@ private static Smz3Randomizer GetRandomizer()
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddTransient()
+ .AddTransient()
.AddTransient()
+ .AddGameModes()
.AddConfigs()
.BuildServiceProvider();
@@ -186,6 +188,6 @@ private static Smz3Randomizer GetRandomizer()
GetLogger(),
serviceProvider.GetRequiredService(),
serviceProvider.GetRequiredService(),
- serviceProvider.GetRequiredService());
+ serviceProvider.GetRequiredService());
}
}
diff --git a/tests/TrackerCouncil.Smz3.Tests/Text/GameHintTests.cs b/tests/TrackerCouncil.Smz3.Tests/Text/GameHintTests.cs
index 98a90107..f572db04 100644
--- a/tests/TrackerCouncil.Smz3.Tests/Text/GameHintTests.cs
+++ b/tests/TrackerCouncil.Smz3.Tests/Text/GameHintTests.cs
@@ -14,9 +14,9 @@
using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda;
using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda.DarkWorld;
using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda.LightWorld;
-using TrackerCouncil.Smz3.SeedGenerator.AltGameModes;
using TrackerCouncil.Smz3.SeedGenerator.Contracts;
using TrackerCouncil.Smz3.SeedGenerator.FileData.Patches;
+using TrackerCouncil.Smz3.SeedGenerator.GameModes;
using TrackerCouncil.Smz3.SeedGenerator.Generation;
using TrackerCouncil.Smz3.SeedGenerator.Infrastructure;
using TrackerCouncil.Smz3.Shared.Enums;
@@ -356,6 +356,26 @@ void SpazerHuntMultiworldHints()
Assert.Equal(LocationUsefulness.Mandatory, hintService.GetUsefulness(worlds[0].Locations.Where(x => x.Room is LightWorldNorthWest.BlindsHideoutRoom).ToList(), worlds, null));
}
+ [Fact]
+ void AllDungeonsHints()
+ {
+ var world = GetVanillaWorld(new GameModeOptions() { SelectedGameModeType = GameModeType.AllDungeons });
+ var hintService = GetGameHintService();
+
+ // Move the bow and boots out of eastern palace for testing
+ var bow = world.FindLocation(LocationId.EasternPalaceBigChest);
+ var bowOtherItem = world.Locations.First(x => x is { Room: LightWorldNorthWest.BlindsHideoutRoom, ItemType: ItemType.ThreeBombs });
+ SwapLocationItems(bow, bowOtherItem);
+ var boots = world.FindLocation(LocationId.Sahasrahla);
+ var bootsOtherItem = world.Locations.First(x => x is { Room: LightWorldNorthWest.BlindsHideoutRoom, ItemType: ItemType.ThreeBombs });
+ SwapLocationItems(boots, bootsOtherItem);
+
+ // Eastern palace should be mandatory for the all dungeons, but useless for vanilla
+ Assert.Equal(LocationUsefulness.Mandatory, hintService.GetUsefulness(world.Locations.Where(x => x.Region is EasternPalace).ToList(), [world], null));
+ world.Config.GameModeOptions.SelectedGameModeType = GameModeType.Vanilla;
+ Assert.Equal(LocationUsefulness.Useless, hintService.GetUsefulness(world.Locations.Where(x => x.Region is EasternPalace).ToList(), [world], null));
+ }
+
#region Private methods
private void SwapLocationItems(Location one, Location two)
{
@@ -433,9 +453,9 @@ private static IGameHintService GetGameHintService()
.AddSingleton()
.AddSingleton()
.AddSingleton()
- .AddTransient()
+ .AddTransient()
.AddTransient()
- .AddGameModes()
+ .AddGameModes()
.AddConfigs()
.BuildServiceProvider();
@@ -454,7 +474,7 @@ internal static IServiceCollection AddGameModes(this IServiceCollecti
lock (s_lockObject)
{
var moduleTypes = typeof(TAssembly).Assembly.GetTypes()
- .Where(x => x.IsSubclassOf(typeof(AltGameModeBase)));
+ .Where(x => x.IsSubclassOf(typeof(GameModeBase)));
foreach (var moduleType in moduleTypes)
{
@@ -462,12 +482,13 @@ internal static IServiceCollection AddGameModes(this IServiceCollecti
if (!s_addedGameModes)
{
- AltGameModeFactory.AddGameModeClass(moduleType);
- s_addedGameModes = true;
+ GameModeFactory.AddGameModeClass(moduleType);
}
}
- services.AddSingleton();
+ s_addedGameModes = true;
+
+ services.AddSingleton();
return services;
}