diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI/Enums/LoadStage.cs | 36 | ||||
-rw-r--r-- | src/SMAPI/Events/ISpecialisedEvents.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Events/LoadStageChangedEventArgs.cs | 31 | ||||
-rw-r--r-- | src/SMAPI/Events/SavePreloadedEventArgs.cs | 7 | ||||
-rw-r--r-- | src/SMAPI/Framework/Events/EventManager.cs | 6 | ||||
-rw-r--r-- | src/SMAPI/Framework/Events/ModSpecialisedEvents.cs | 8 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 13 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 89 | ||||
-rw-r--r-- | src/SMAPI/Patches/LoadForNewGamePatch.cs | 109 | ||||
-rw-r--r-- | src/SMAPI/StardewModdingAPI.csproj | 4 |
10 files changed, 254 insertions, 53 deletions
diff --git a/src/SMAPI/Enums/LoadStage.cs b/src/SMAPI/Enums/LoadStage.cs new file mode 100644 index 00000000..6ff7de4f --- /dev/null +++ b/src/SMAPI/Enums/LoadStage.cs @@ -0,0 +1,36 @@ +namespace StardewModdingAPI.Enums +{ + /// <summary>A low-level stage in the game's loading process.</summary> + public enum LoadStage + { + /// <summary>A save is not loaded or loading.</summary> + None, + + /// <summary>The game is creating a new save slot, and has initialised the basic save info.</summary> + CreatedBasicInfo, + + /// <summary>The game is creating a new save slot, and has initialised the in-game locations.</summary> + CreatedLocations, + + /// <summary>The game is creating a new save slot, and has created the physical save files.</summary> + CreatedSaveFile, + + /// <summary>The game is loading a save slot, and has read the raw save data into <see cref="StardewValley.SaveGame.loaded"/>. Not applicable when connecting to a multiplayer host. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 20.</summary> + SaveParsed, + + /// <summary>The game is loading a save slot, and has applied the basic save info (including player data). Not applicable when connecting to a multiplayer host. Note that some basic info (like daily luck) is not initialised at this point. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 36.</summary> + SaveLoadedBasicInfo, + + /// <summary>The game is loading a save slot, and has applied the in-game location data. Not applicable when connecting to a multiplayer host. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 50.</summary> + SaveLoadedLocations, + + /// <summary>The final metadata has been loaded from the save file. This happens before the game applies problem fixes, checks for achievements, starts music, etc. Not applicable when connecting to a multiplayer host.</summary> + Preloaded, + + /// <summary>The save is fully loaded, but the world may not be fully initialised yet.</summary> + Loaded, + + /// <summary>The save is fully loaded, the world has been initialised, and <see cref="Context.IsWorldReady"/> is now true.</summary> + Ready + } +} diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/ISpecialisedEvents.cs index 2a19113c..ecb109e6 100644 --- a/src/SMAPI/Events/ISpecialisedEvents.cs +++ b/src/SMAPI/Events/ISpecialisedEvents.cs @@ -5,8 +5,8 @@ namespace StardewModdingAPI.Events /// <summary>Events serving specialised edge cases that shouldn't be used by most mods.</summary> public interface ISpecialisedEvents { - /// <summary>Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet.</summary> - event EventHandler<SavePreloadedEventArgs> SavePreloaded; + /// <summary>Raised when the low-level stage in the game's loading process has changed. This is an advanced event for mods which need to run code at specific points in the loading process. The available stages or when they happen might change without warning in future versions (e.g. due to changes in the game's load process), so mods using this event are more likely to break or have bugs. Most mods should use <see cref="IGameLoopEvents"/> instead.</summary> + event EventHandler<LoadStageChangedEventArgs> LoadStageChanged; /// <summary>Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console.</summary> event EventHandler<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking; diff --git a/src/SMAPI/Events/LoadStageChangedEventArgs.cs b/src/SMAPI/Events/LoadStageChangedEventArgs.cs new file mode 100644 index 00000000..e837a5f1 --- /dev/null +++ b/src/SMAPI/Events/LoadStageChangedEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using StardewModdingAPI.Enums; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for an <see cref="ISpecialisedEvents.LoadStageChanged"/> event.</summary> + public class LoadStageChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The previous load stage.</summary> + public LoadStage OldStage { get; } + + /// <summary>The new load stage.</summary> + public LoadStage NewStage { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="old">The previous load stage.</param> + /// <param name="current">The new load stage.</param> + public LoadStageChangedEventArgs(LoadStage old, LoadStage current) + { + this.OldStage = old; + this.NewStage = current; + } + } +} diff --git a/src/SMAPI/Events/SavePreloadedEventArgs.cs b/src/SMAPI/Events/SavePreloadedEventArgs.cs deleted file mode 100644 index 03990f5a..00000000 --- a/src/SMAPI/Events/SavePreloadedEventArgs.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace StardewModdingAPI.Events -{ - /// <summary>Event arguments for an <see cref="ISpecialisedEvents.SavePreloaded"/> event.</summary> - public class SavePreloadedEventArgs : EventArgs { } -} diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index bd862046..b7f00f52 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -151,8 +151,8 @@ namespace StardewModdingAPI.Framework.Events /**** ** Specialised ****/ - /// <summary>Raised immediately after the player loads a save slot, but before the world is fully initialised.</summary> - public readonly ManagedEvent<SavePreloadedEventArgs> SavePreloaded; + /// <summary>Raised when the low-level stage in the game's loading process has changed. See notes on <see cref="ISpecialisedEvents.LoadStageChanged"/>.</summary> + public readonly ManagedEvent<LoadStageChangedEventArgs> LoadStageChanged; /// <summary>Raised before the game performs its overall update tick (≈60 times per second). See notes on <see cref="ISpecialisedEvents.UnvalidatedUpdateTicking"/>.</summary> public readonly ManagedEvent<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking; @@ -411,7 +411,7 @@ namespace StardewModdingAPI.Framework.Events this.ObjectListChanged = ManageEventOf<ObjectListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); this.TerrainFeatureListChanged = ManageEventOf<TerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); - this.SavePreloaded = ManageEventOf<SavePreloadedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.SavePreloaded)); + this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.LoadStageChanged)); this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking)); this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked)); diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs index 83e349cf..7c3e9dee 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -9,11 +9,11 @@ namespace StardewModdingAPI.Framework.Events /********* ** Accessors *********/ - /// <summary>Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet.</summary> - public event EventHandler<SavePreloadedEventArgs> SavePreloaded + /// <summary>Raised when the low-level stage in the game's loading process has changed. This is an advanced event for mods which need to run code at specific points in the loading process. The available stages or when they happen might change without warning in future versions (e.g. due to changes in the game's load process), so mods using this event are more likely to break or have bugs. Most mods should use <see cref="IGameLoopEvents"/> instead.</summary> + public event EventHandler<LoadStageChangedEventArgs> LoadStageChanged { - add => this.EventManager.SavePreloaded.Add(value); - remove => this.EventManager.SavePreloaded.Remove(value); + add => this.EventManager.LoadStageChanged.Add(value); + remove => this.EventManager.LoadStageChanged.Remove(value); } /// <summary>Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console.</summary> diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 679838ba..00801b72 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -181,12 +181,6 @@ namespace StardewModdingAPI.Framework return; } #endif - - // apply game patches - new GamePatcher(this.Monitor).Apply( - new DialogueErrorPatch(this.MonitorForGame, this.Reflection), - new ObjectErrorPatch() - ); } /// <summary>Launch SMAPI.</summary> @@ -237,6 +231,13 @@ namespace StardewModdingAPI.Framework this.GameInstance = new SGame(this.Monitor, this.MonitorForGame, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, SCore.DeprecationManager, this.OnLocaleChanged, this.InitialiseAfterGameStart, this.Dispose); StardewValley.Program.gamePtr = this.GameInstance; + // apply game patches + new GamePatcher(this.Monitor).Apply( + new DialogueErrorPatch(this.MonitorForGame, this.Reflection), + new ObjectErrorPatch(), + new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged) + ); + // add exit handler new Thread(() => { diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index befd9cef..cb62de2a 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -69,11 +69,8 @@ namespace StardewModdingAPI.Framework /// <remarks>Skipping a few frames ensures the game finishes initialising the world before mods try to change it.</remarks> private readonly Countdown AfterLoadTimer = new Countdown(5); - /// <summary>Whether <see cref="EventManager.SavePreloaded"/> was raised for this session.</summary> - private bool RaisedPreloadedEvent; - - /// <summary>Whether the after-load events were raised for this session.</summary> - private bool RaisedLoadedEvent; + /// <summary>The current stage in the game's loading process.</summary> + private LoadStage LoadStage = LoadStage.None; /// <summary>Whether the game is saving and SMAPI has already raised <see cref="IGameLoopEvents.Saving"/>.</summary> private bool IsBetweenSaveEvents; @@ -216,16 +213,33 @@ namespace StardewModdingAPI.Framework this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID)); } - /// <summary>A callback raised when the player quits a save and returns to the title screen.</summary> - private void OnReturnedToTitle() + /// <summary>A callback invoked when the game's low-level load stage changes.</summary> + /// <param name="newStage">The new load stage.</param> + internal void OnLoadStageChanged(LoadStage newStage) { - this.Monitor.Log("Context: returned to title", LogLevel.Trace); - this.RaisedPreloadedEvent = false; - this.Multiplayer.CleanupOnMultiplayerExit(); - this.Events.ReturnedToTitle.RaiseEmpty(); + // nothing to do + if (newStage == this.LoadStage) + return; + + // update data + LoadStage oldStage = this.LoadStage; + this.LoadStage = newStage; + if (newStage == LoadStage.None) + { + this.Monitor.Log("Context: returned to title", LogLevel.Trace); + this.Multiplayer.CleanupOnMultiplayerExit(); + } + this.Monitor.VerboseLog($"Context: load stage changed to {newStage}"); + + // raise events + this.Events.LoadStageChanged.Raise(new LoadStageChangedEventArgs(oldStage, newStage)); + if (newStage == LoadStage.None) + { + this.Events.ReturnedToTitle.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_AfterReturnToTitle.Raise(); + this.Events.Legacy_AfterReturnToTitle.Raise(); #endif + } } /// <summary>Constructor a content manager to read XNB files.</summary> @@ -284,7 +298,29 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log("Game loader synchronising...", LogLevel.Trace); while (Game1.currentLoader?.MoveNext() == true) - ; + { + // raise load stage changed + switch (Game1.currentLoader.Current) + { + case 20: + this.OnLoadStageChanged(LoadStage.SaveParsed); + break; + + case 36: + this.OnLoadStageChanged(LoadStage.SaveLoadedBasicInfo); + break; + + case 50: + this.OnLoadStageChanged(LoadStage.SaveLoadedLocations); + break; + + default: + if (Game1.gameMode == Game1.playingGameMode) + this.OnLoadStageChanged(LoadStage.Preloaded); + break; + } + } + Game1.currentLoader = null; this.Monitor.Log("Game loader done.", LogLevel.Trace); } @@ -411,6 +447,7 @@ namespace StardewModdingAPI.Framework // raise after-create this.IsBetweenCreateEvents = false; this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); + this.OnLoadStageChanged(LoadStage.CreatedSaveFile); this.Events.SaveCreated.RaiseEmpty(); #if !SMAPI_3_0_STRICT this.Events.Legacy_AfterCreateSave.Raise(); @@ -434,7 +471,10 @@ namespace StardewModdingAPI.Framework *********/ bool wasWorldReady = Context.IsWorldReady; if ((Context.IsWorldReady && !Context.IsSaveLoaded) || Game1.exitToTitle) - this.MarkWorldNotReady(); + { + Context.IsWorldReady = false; + this.AfterLoadTimer.Reset(); + } else if (Context.IsSaveLoaded && this.AfterLoadTimer.Current > 0 && Game1.currentLocation != null) { if (Game1.dayOfMonth != 0) // wait until new-game intro finishes (world not fully initialised yet) @@ -469,8 +509,8 @@ namespace StardewModdingAPI.Framework ** Load / return-to-title events *********/ if (wasWorldReady && !Context.IsWorldReady) - this.OnReturnedToTitle(); - else if (!this.RaisedLoadedEvent && Context.IsWorldReady) + this.OnLoadStageChanged(LoadStage.None); + else if (Context.IsWorldReady && this.LoadStage != LoadStage.Ready) { // print context string context = $"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}."; @@ -484,7 +524,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log(context, LogLevel.Trace); // raise events - this.RaisedLoadedEvent = true; + this.OnLoadStageChanged(LoadStage.Ready); this.Events.SaveLoaded.RaiseEmpty(); this.Events.DayStarted.RaiseEmpty(); #if !SMAPI_3_0_STRICT @@ -834,11 +874,8 @@ namespace StardewModdingAPI.Framework this.Events.GameLaunched.Raise(new GameLaunchedEventArgs()); // preloaded - if (Context.IsSaveLoaded && !this.RaisedPreloadedEvent) - { - this.RaisedPreloadedEvent = true; - this.Events.SavePreloaded.RaiseEmpty(); - } + if (Context.IsSaveLoaded && this.LoadStage != LoadStage.Loaded && this.LoadStage != LoadStage.Ready) + this.OnLoadStageChanged(LoadStage.Loaded); // update tick this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); @@ -1649,14 +1686,6 @@ namespace StardewModdingAPI.Framework /**** ** Methods ****/ - /// <summary>Perform any cleanup needed when a save is unloaded.</summary> - private void MarkWorldNotReady() - { - Context.IsWorldReady = false; - this.AfterLoadTimer.Reset(); - this.RaisedLoadedEvent = false; - } - #if !SMAPI_3_0_STRICT /// <summary>Raise the <see cref="GraphicsEvents.OnPostRenderEvent"/> if there are any listeners.</summary> /// <param name="needsNewBatch">Whether to create a new sprite batch.</param> diff --git a/src/SMAPI/Patches/LoadForNewGamePatch.cs b/src/SMAPI/Patches/LoadForNewGamePatch.cs new file mode 100644 index 00000000..9e788e84 --- /dev/null +++ b/src/SMAPI/Patches/LoadForNewGamePatch.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Reflection; +using Harmony; +using StardewModdingAPI.Enums; +using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; +using StardewValley.Menus; + +namespace StardewModdingAPI.Patches +{ + /// <summary>A Harmony patch for <see cref="Game1.loadForNewGame"/> which notifies SMAPI for save creation load stages.</summary> + /// <remarks>This patch hooks into <see cref="Game1.loadForNewGame"/>, checks if <c>TitleMenu.transitioningCharacterCreationMenu</c> is true (which means the player is creating a new save file), then raises <see cref="LoadStage.CreatedBasicInfo"/> after the location list is cleared twice (the second clear happens right before locations are created), and <see cref="LoadStage.CreatedLocations"/> when the method ends.</remarks> + internal class LoadForNewGamePatch : IHarmonyPatch + { + /********* + ** Accessors + *********/ + /// <summary>Simplifies access to private code.</summary> + private static Reflector Reflection; + + /// <summary>A callback to invoke when the load stage changes.</summary> + private static Action<LoadStage> OnStageChanged; + + /// <summary>Whether <see cref="Game1.loadForNewGame"/> was called as part of save creation.</summary> + private static bool IsCreating; + + /// <summary>The number of times that <see cref="Game1.locations"/> has been cleared since <see cref="Game1.loadForNewGame"/> started.</summary> + private static int TimesLocationsCleared = 0; + + + /********* + ** Accessors + *********/ + /// <summary>A unique name for this patch.</summary> + public string Name => $"{nameof(LoadForNewGamePatch)}"; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="reflection">Simplifies access to private code.</param> + /// <param name="onStageChanged">A callback to invoke when the load stage changes.</param> + public LoadForNewGamePatch(Reflector reflection, Action<LoadStage> onStageChanged) + { + LoadForNewGamePatch.Reflection = reflection; + LoadForNewGamePatch.OnStageChanged = onStageChanged; + } + + /// <summary>Apply the Harmony patch.</summary> + /// <param name="harmony">The Harmony instance.</param> + public void Apply(HarmonyInstance harmony) + { + MethodInfo method = AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)); + MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Prefix)); + MethodInfo postfix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Postfix)); + + harmony.Patch(method, new HarmonyMethod(prefix), new HarmonyMethod(postfix)); + } + + + /********* + ** Private methods + *********/ + /// <summary>The method to call instead of <see cref="Game1.loadForNewGame"/>.</summary> + /// <returns>Returns whether to execute the original method.</returns> + /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> + private static bool Prefix() + { + LoadForNewGamePatch.IsCreating = Game1.activeClickableMenu is TitleMenu menu && LoadForNewGamePatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue(); + LoadForNewGamePatch.TimesLocationsCleared = 0; + if (LoadForNewGamePatch.IsCreating) + { + // raise CreatedBasicInfo after locations are cleared twice + ObservableCollection<GameLocation> locations = (ObservableCollection<GameLocation>)Game1.locations; + locations.CollectionChanged += LoadForNewGamePatch.OnLocationListChanged; + } + + return true; + } + + /// <summary>The method to call instead after <see cref="Game1.loadForNewGame"/>.</summary> + /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> + private static void Postfix() + { + if (LoadForNewGamePatch.IsCreating) + { + // clean up + ObservableCollection<GameLocation> locations = (ObservableCollection<GameLocation>) Game1.locations; + locations.CollectionChanged -= LoadForNewGamePatch.OnLocationListChanged; + + // raise stage changed + LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedLocations); + } + } + + /// <summary>Raised when <see cref="Game1.locations"/> changes.</summary> + /// <param name="sender">The event sender.</param> + /// <param name="e">The event arguments.</param> + private static void OnLocationListChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (++LoadForNewGamePatch.TimesLocationsCleared == 2) + LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedBasicInfo); + } + } +} diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 36fa7e0b..fdb0c6c7 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -76,6 +76,7 @@ <Compile Include="..\..\build\GlobalAssemblyInfo.cs"> <Link>Properties\GlobalAssemblyInfo.cs</Link> </Compile> + <Compile Include="Enums\LoadStage.cs" /> <Compile Include="Enums\SkillType.cs" /> <Compile Include="Events\BuildingListChangedEventArgs.cs" /> <Compile Include="Events\ButtonPressedEventArgs.cs" /> @@ -123,6 +124,7 @@ <Compile Include="Events\IWorldEvents.cs" /> <Compile Include="Events\LargeTerrainFeatureListChangedEventArgs.cs" /> <Compile Include="Events\LevelChangedEventArgs.cs" /> + <Compile Include="Events\LoadStageChangedEventArgs.cs" /> <Compile Include="Events\LocationEvents.cs" /> <Compile Include="Events\LocationListChangedEventArgs.cs" /> <Compile Include="Events\MenuChangedEventArgs.cs" /> @@ -150,7 +152,6 @@ <Compile Include="Events\SavedEventArgs.cs" /> <Compile Include="Events\SaveEvents.cs" /> <Compile Include="Events\SaveLoadedEventArgs.cs" /> - <Compile Include="Events\SavePreloadedEventArgs.cs" /> <Compile Include="Events\SavingEventArgs.cs" /> <Compile Include="Events\SpecialisedEvents.cs" /> <Compile Include="Events\TerrainFeatureListChangedEventArgs.cs" /> @@ -327,6 +328,7 @@ <Compile Include="Framework\Monitor.cs" /> <Compile Include="Metadata\InstructionMetadata.cs" /> <Compile Include="Mod.cs" /> + <Compile Include="Patches\LoadForNewGamePatch.cs" /> <Compile Include="Patches\ObjectErrorPatch.cs" /> <Compile Include="Patches\DialogueErrorPatch.cs" /> <Compile Include="PatchMode.cs" /> |