Merge branch 'develop' into stable
This commit is contained in:
commit
5953fc3bd0
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<!--set properties -->
|
<!--set properties -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>3.8.3</Version>
|
<Version>3.8.4</Version>
|
||||||
<Product>SMAPI</Product>
|
<Product>SMAPI</Product>
|
||||||
|
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
|
@ -7,6 +7,16 @@
|
||||||
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 3.8.4
|
||||||
|
Released 15 January 2021 for Stardew Valley 1.5.3 or later.
|
||||||
|
|
||||||
|
* For players:
|
||||||
|
* Updated for Stardew Valley 1.5.3.
|
||||||
|
* Fixed issue where title screen music didn't stop after loading a save.
|
||||||
|
|
||||||
|
* For modders:
|
||||||
|
* Fixed `SemanticVersion` comparisons returning wrong value in rare cases.
|
||||||
|
|
||||||
## 3.8.3
|
## 3.8.3
|
||||||
Released 08 January 2021 for Stardew Valley 1.5.2 or later.
|
Released 08 January 2021 for Stardew Valley 1.5.2 or later.
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Console Commands",
|
"Name": "Console Commands",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.8.3",
|
"Version": "3.8.4",
|
||||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||||
"UniqueID": "SMAPI.ConsoleCommands",
|
"UniqueID": "SMAPI.ConsoleCommands",
|
||||||
"EntryDll": "ConsoleCommands.dll",
|
"EntryDll": "ConsoleCommands.dll",
|
||||||
"MinimumApiVersion": "3.8.3"
|
"MinimumApiVersion": "3.8.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Save Backup",
|
"Name": "Save Backup",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.8.3",
|
"Version": "3.8.4",
|
||||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||||
"UniqueID": "SMAPI.SaveBackup",
|
"UniqueID": "SMAPI.SaveBackup",
|
||||||
"EntryDll": "SaveBackup.dll",
|
"EntryDll": "SaveBackup.dll",
|
||||||
"MinimumApiVersion": "3.8.3"
|
"MinimumApiVersion": "3.8.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,38 +230,49 @@ namespace StardewModdingAPI.Toolkit
|
||||||
const int curNewer = 1;
|
const int curNewer = 1;
|
||||||
const int curOlder = -1;
|
const int curOlder = -1;
|
||||||
|
|
||||||
// compare stable versions
|
int CompareToRaw()
|
||||||
if (this.MajorVersion != otherMajor)
|
|
||||||
return this.MajorVersion.CompareTo(otherMajor);
|
|
||||||
if (this.MinorVersion != otherMinor)
|
|
||||||
return this.MinorVersion.CompareTo(otherMinor);
|
|
||||||
if (this.PatchVersion != otherPatch)
|
|
||||||
return this.PatchVersion.CompareTo(otherPatch);
|
|
||||||
if (this.PlatformRelease != otherPlatformRelease)
|
|
||||||
return this.PlatformRelease.CompareTo(otherPlatformRelease);
|
|
||||||
if (this.PrereleaseTag == otherTag)
|
|
||||||
return same;
|
|
||||||
|
|
||||||
// stable supersedes prerelease
|
|
||||||
bool curIsStable = string.IsNullOrWhiteSpace(this.PrereleaseTag);
|
|
||||||
bool otherIsStable = string.IsNullOrWhiteSpace(otherTag);
|
|
||||||
if (curIsStable)
|
|
||||||
return curNewer;
|
|
||||||
if (otherIsStable)
|
|
||||||
return curOlder;
|
|
||||||
|
|
||||||
// compare two prerelease tag values
|
|
||||||
string[] curParts = this.PrereleaseTag.Split('.', '-');
|
|
||||||
string[] otherParts = otherTag.Split('.', '-');
|
|
||||||
for (int i = 0; i < curParts.Length; i++)
|
|
||||||
{
|
{
|
||||||
// longer prerelease tag supersedes if otherwise equal
|
// compare stable versions
|
||||||
if (otherParts.Length <= i)
|
if (this.MajorVersion != otherMajor)
|
||||||
return curNewer;
|
return this.MajorVersion.CompareTo(otherMajor);
|
||||||
|
if (this.MinorVersion != otherMinor)
|
||||||
|
return this.MinorVersion.CompareTo(otherMinor);
|
||||||
|
if (this.PatchVersion != otherPatch)
|
||||||
|
return this.PatchVersion.CompareTo(otherPatch);
|
||||||
|
if (this.PlatformRelease != otherPlatformRelease)
|
||||||
|
return this.PlatformRelease.CompareTo(otherPlatformRelease);
|
||||||
|
if (this.PrereleaseTag == otherTag)
|
||||||
|
return same;
|
||||||
|
|
||||||
// compare if different
|
// stable supersedes prerelease
|
||||||
if (curParts[i] != otherParts[i])
|
bool curIsStable = string.IsNullOrWhiteSpace(this.PrereleaseTag);
|
||||||
|
bool otherIsStable = string.IsNullOrWhiteSpace(otherTag);
|
||||||
|
if (curIsStable)
|
||||||
|
return curNewer;
|
||||||
|
if (otherIsStable)
|
||||||
|
return curOlder;
|
||||||
|
|
||||||
|
// compare two prerelease tag values
|
||||||
|
string[] curParts = this.PrereleaseTag.Split('.', '-');
|
||||||
|
string[] otherParts = otherTag.Split('.', '-');
|
||||||
|
int length = Math.Max(curParts.Length, otherParts.Length);
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
|
// longer prerelease tag supersedes if otherwise equal
|
||||||
|
if (curParts.Length <= i)
|
||||||
|
return curOlder;
|
||||||
|
if (otherParts.Length <= i)
|
||||||
|
return curNewer;
|
||||||
|
|
||||||
|
// skip if same value, unless we've reached the end
|
||||||
|
if (curParts[i] == otherParts[i])
|
||||||
|
{
|
||||||
|
if (i == length - 1)
|
||||||
|
return same;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// unofficial is always lower-precedence
|
// unofficial is always lower-precedence
|
||||||
if (otherParts[i].Equals("unofficial", StringComparison.OrdinalIgnoreCase))
|
if (otherParts[i].Equals("unofficial", StringComparison.OrdinalIgnoreCase))
|
||||||
return curNewer;
|
return curNewer;
|
||||||
|
@ -277,10 +288,17 @@ namespace StardewModdingAPI.Toolkit
|
||||||
// else compare lexically
|
// else compare lexically
|
||||||
return string.Compare(curParts[i], otherParts[i], StringComparison.OrdinalIgnoreCase);
|
return string.Compare(curParts[i], otherParts[i], StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fallback (this should never happen)
|
||||||
|
return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherPlatformRelease, otherTag).ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback (this should never happen)
|
return CompareToRaw() switch
|
||||||
return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherPlatformRelease, otherTag).ToString(), StringComparison.OrdinalIgnoreCase);
|
{
|
||||||
|
(< 0) => curOlder,
|
||||||
|
(> 0) => curNewer,
|
||||||
|
_ => same
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Assert that the current version is valid.</summary>
|
/// <summary>Assert that the current version is valid.</summary>
|
||||||
|
|
|
@ -48,6 +48,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "technical", "technical", "{
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internal", "Internal", "{82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mods", "Mods", "{AE9A4D46-E910-4293-8BC4-673F85FFF6A5}"
|
||||||
|
EndProject
|
||||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SMAPI.Internal", "SMAPI.Internal\SMAPI.Internal.shproj", "{85208F8D-6FD1-4531-BE05-7142490F59FE}"
|
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SMAPI.Internal", "SMAPI.Internal\SMAPI.Internal.shproj", "{85208F8D-6FD1-4531-BE05-7142490F59FE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.ModBuildConfig.Analyzer.Tests", "SMAPI.ModBuildConfig.Analyzer.Tests\SMAPI.ModBuildConfig.Analyzer.Tests.csproj", "{680B2641-81EA-467C-86A5-0E81CDC57ED0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.ModBuildConfig.Analyzer.Tests", "SMAPI.ModBuildConfig.Analyzer.Tests\SMAPI.ModBuildConfig.Analyzer.Tests.csproj", "{680B2641-81EA-467C-86A5-0E81CDC57ED0}"
|
||||||
|
@ -148,6 +150,8 @@ Global
|
||||||
{85208F8D-6FD1-4531-BE05-7142490F59FE} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
|
{85208F8D-6FD1-4531-BE05-7142490F59FE} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
|
||||||
{680B2641-81EA-467C-86A5-0E81CDC57ED0} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
|
{680B2641-81EA-467C-86A5-0E81CDC57ED0} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
|
||||||
{AA95884B-7097-476E-92C8-D0500DE9D6D1} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
|
{AA95884B-7097-476E-92C8-D0500DE9D6D1} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11}
|
||||||
|
{0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5}
|
||||||
|
{CD53AD6F-97F4-4872-A212-50C2A0FD3601} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {70143042-A862-47A8-A677-7C819DDC90DC}
|
SolutionGuid = {70143042-A862-47A8-A677-7C819DDC90DC}
|
||||||
|
|
|
@ -54,10 +54,10 @@ namespace StardewModdingAPI
|
||||||
** Public
|
** Public
|
||||||
****/
|
****/
|
||||||
/// <summary>SMAPI's current semantic version.</summary>
|
/// <summary>SMAPI's current semantic version.</summary>
|
||||||
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.8.3");
|
public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.8.4");
|
||||||
|
|
||||||
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
/// <summary>The minimum supported version of Stardew Valley.</summary>
|
||||||
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.2");
|
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.3");
|
||||||
|
|
||||||
/// <summary>The maximum supported version of Stardew Valley.</summary>
|
/// <summary>The maximum supported version of Stardew Valley.</summary>
|
||||||
public static ISemanticVersion MaximumGameVersion { get; } = null;
|
public static ISemanticVersion MaximumGameVersion { get; } = null;
|
||||||
|
|
|
@ -619,6 +619,7 @@ namespace StardewModdingAPI.Framework
|
||||||
if (Game1.currentLoader != null)
|
if (Game1.currentLoader != null)
|
||||||
{
|
{
|
||||||
this.Monitor.Log("Game loader synchronizing...");
|
this.Monitor.Log("Game loader synchronizing...");
|
||||||
|
this.Reflection.GetMethod(Game1.game1, "UpdateTitleScreen").Invoke(Game1.currentGameTime); // run game logic to change music on load, etc
|
||||||
while (Game1.currentLoader?.MoveNext() == true)
|
while (Game1.currentLoader?.MoveNext() == true)
|
||||||
{
|
{
|
||||||
// raise load stage changed
|
// raise load stage changed
|
||||||
|
|
|
@ -740,16 +740,20 @@ namespace StardewModdingAPI.Framework
|
||||||
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(startingX, (int)y, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Microsoft.Xna.Framework.Color.Red * 0.5f);
|
Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle(startingX, (int)y, Game1.graphics.GraphicsDevice.Viewport.Width, 1), Microsoft.Xna.Framework.Color.Red * 0.5f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Game1.ShouldShowOnscreenUsernames() && Game1.currentLocation != null)
|
||||||
|
{
|
||||||
|
Game1.currentLocation.DrawFarmerUsernames(Game1.spriteBatch);
|
||||||
|
}
|
||||||
if (Game1.currentBillboard != 0 && !this.takingMapScreenshot)
|
if (Game1.currentBillboard != 0 && !this.takingMapScreenshot)
|
||||||
{
|
{
|
||||||
this.drawBillboard();
|
this.drawBillboard();
|
||||||
}
|
}
|
||||||
if (!Game1.eventUp && Game1.farmEvent == null && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !this.takingMapScreenshot && Game1.isOutdoorMapSmallerThanViewport())
|
if (!Game1.eventUp && Game1.farmEvent == null && Game1.currentBillboard == 0 && Game1.gameMode == 3 && !this.takingMapScreenshot && Game1.isOutdoorMapSmallerThanViewport())
|
||||||
{
|
{
|
||||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, -Math.Min(Game1.viewport.X, 4096), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
|
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, -Math.Min(Game1.viewport.X, GameRunner.MaxTextureSize), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
|
||||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64, 0, Math.Min(4096, Game1.graphics.GraphicsDevice.Viewport.Width - (-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64)), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
|
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64, 0, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Width - (-Game1.viewport.X + Game1.currentLocation.map.Layers[0].LayerWidth * 64)), Game1.graphics.GraphicsDevice.Viewport.Height), Microsoft.Xna.Framework.Color.Black);
|
||||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, Game1.graphics.GraphicsDevice.Viewport.Width, -Math.Min(Game1.viewport.Y, 4096)), Microsoft.Xna.Framework.Color.Black);
|
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, 0, Game1.graphics.GraphicsDevice.Viewport.Width, -Math.Min(Game1.viewport.Y, GameRunner.MaxTextureSize)), Microsoft.Xna.Framework.Color.Black);
|
||||||
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, -Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64, Game1.graphics.GraphicsDevice.Viewport.Width, Math.Min(4096, Game1.graphics.GraphicsDevice.Viewport.Height - (-Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64))), Microsoft.Xna.Framework.Color.Black);
|
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle(0, -Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64, Game1.graphics.GraphicsDevice.Viewport.Width, Math.Min(GameRunner.MaxTextureSize, Game1.graphics.GraphicsDevice.Viewport.Height - (-Game1.viewport.Y + Game1.currentLocation.map.Layers[0].LayerHeight * 64))), Microsoft.Xna.Framework.Color.Black);
|
||||||
}
|
}
|
||||||
Game1.spriteBatch.End();
|
Game1.spriteBatch.End();
|
||||||
Game1.PushUIMode();
|
Game1.PushUIMode();
|
||||||
|
|
|
@ -138,36 +138,7 @@ namespace StardewModdingAPI.Metadata
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(location.mapPath.Value) && this.NormalizeAssetNameIgnoringEmpty(location.mapPath.Value) == key)
|
if (!string.IsNullOrWhiteSpace(location.mapPath.Value) && this.NormalizeAssetNameIgnoringEmpty(location.mapPath.Value) == key)
|
||||||
{
|
{
|
||||||
// reset patch caches
|
this.ReloadMap(location);
|
||||||
switch (location)
|
|
||||||
{
|
|
||||||
case Town _:
|
|
||||||
this.Reflection.GetField<bool>(location, "ccRefurbished").SetValue(false);
|
|
||||||
this.Reflection.GetField<bool>(location, "isShowingDestroyedJoja").SetValue(false);
|
|
||||||
this.Reflection.GetField<bool>(location, "isShowingUpgradedPamHouse").SetValue(false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Beach _:
|
|
||||||
case BeachNightMarket _:
|
|
||||||
case Forest _:
|
|
||||||
this.Reflection.GetField<bool>(location, "hasShownCCUpgrade").SetValue(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// general updates
|
|
||||||
location.reloadMap();
|
|
||||||
location.updateSeasonalTileSheets();
|
|
||||||
location.updateWarps();
|
|
||||||
|
|
||||||
// update interior doors
|
|
||||||
location.interiorDoors.Clear();
|
|
||||||
foreach (var entry in new InteriorDoorDictionary(location))
|
|
||||||
location.interiorDoors.Add(entry);
|
|
||||||
|
|
||||||
// update doors
|
|
||||||
location.doors.Clear();
|
|
||||||
location.updateDoors();
|
|
||||||
|
|
||||||
anyChanged = true;
|
anyChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -803,6 +774,41 @@ namespace StardewModdingAPI.Metadata
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Reload the map for a location.</summary>
|
||||||
|
/// <param name="location">The location whose map to reload.</param>
|
||||||
|
private void ReloadMap(GameLocation location)
|
||||||
|
{
|
||||||
|
// reset patch caches
|
||||||
|
switch (location)
|
||||||
|
{
|
||||||
|
case Town _:
|
||||||
|
this.Reflection.GetField<bool>(location, "ccRefurbished").SetValue(false);
|
||||||
|
this.Reflection.GetField<bool>(location, "isShowingDestroyedJoja").SetValue(false);
|
||||||
|
this.Reflection.GetField<bool>(location, "isShowingUpgradedPamHouse").SetValue(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Beach _:
|
||||||
|
case BeachNightMarket _:
|
||||||
|
case Forest _:
|
||||||
|
this.Reflection.GetField<bool>(location, "hasShownCCUpgrade").SetValue(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// general updates
|
||||||
|
location.reloadMap();
|
||||||
|
location.updateSeasonalTileSheets();
|
||||||
|
location.updateWarps();
|
||||||
|
|
||||||
|
// update interior doors
|
||||||
|
location.interiorDoors.Clear();
|
||||||
|
foreach (var entry in new InteriorDoorDictionary(location))
|
||||||
|
location.interiorDoors.Add(entry);
|
||||||
|
|
||||||
|
// update doors
|
||||||
|
location.doors.Clear();
|
||||||
|
location.updateDoors();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Reload the disposition data for matching NPCs.</summary>
|
/// <summary>Reload the disposition data for matching NPCs.</summary>
|
||||||
/// <param name="content">The content manager through which to reload the asset.</param>
|
/// <param name="content">The content manager through which to reload the asset.</param>
|
||||||
/// <param name="key">The asset key to reload.</param>
|
/// <param name="key">The asset key to reload.</param>
|
||||||
|
|
Loading…
Reference in New Issue