Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions DynamicReflections/DynamicReflections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public class DynamicReflections : Mod
internal static bool isDrawingWaterReflection;
internal static bool isFilteringWater;
internal static bool shouldSkipWaterOverlay;
internal static bool shouldDeferWaterReflectionPresentation;
internal static bool shouldDeferSkyReflectionPresentation;
internal static bool isRenderingTopmostBackgroundWaterMask;

// Puddle reflection variables
internal static bool shouldDrawPuddlesReflection;
Expand Down Expand Up @@ -101,6 +104,7 @@ public class DynamicReflections : Mod
internal static RenderTarget2D mirrorsLayerRenderTarget;
internal static RenderTarget2D mirrorsFurnitureRenderTarget;
internal static RenderTarget2D puddlesRenderTarget;
internal static RenderTarget2D backgroundWaterMaskRenderTarget;
internal static RasterizerState rasterizer;

public override void Entry(IModHelper helper)
Expand Down Expand Up @@ -166,6 +170,7 @@ public override object GetApi()
private void OnWindowResized(object sender, StardewModdingAPI.Events.WindowResizedEventArgs e)
{
LoadRenderers();
LayerToolkit.InvalidateCaches();
}

private void OnButtonPressed(object sender, StardewModdingAPI.Events.ButtonPressedEventArgs e)
Expand Down Expand Up @@ -240,6 +245,8 @@ private void OnFurnitureListChanged(object sender, StardewModdingAPI.Events.Furn

private void OnWarped(object sender, StardewModdingAPI.Events.WarpedEventArgs e)
{
LayerToolkit.InvalidateCaches();

SetSkyReflectionSettings();
SetPuddleReflectionSettings();
SetWaterReflectionSettings();
Expand Down Expand Up @@ -1373,6 +1380,7 @@ internal void LoadRenderers()
RegenerateRenderer(ref playerWaterReflectionRender, shouldUseScreenDimensions);
RegenerateRenderer(ref playerPuddleReflectionRender, shouldUseScreenDimensions);
RegenerateRenderer(ref puddlesRenderTarget, shouldUseScreenDimensions);
RegenerateRenderer(ref backgroundWaterMaskRenderTarget, shouldUseScreenDimensions);

RegenerateRenderer(ref mirrorsLayerRenderTarget, shouldUseScreenDimensions);
RegenerateRenderer(ref mirrorsFurnitureRenderTarget, shouldUseScreenDimensions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal void Apply(Harmony harmony)
{
harmony.Patch(AccessTools.Method(_type, nameof(GameLocation.drawWater), new[] { typeof(SpriteBatch) }), prefix: new HarmonyMethod(GetType(), nameof(DrawWaterPrefix)));
harmony.Patch(AccessTools.Method(_type, nameof(GameLocation.drawWater), new[] { typeof(SpriteBatch) }), prefix: new HarmonyMethod(GetType(), nameof(VisibleFishDrawPrefix)));
harmony.Patch(AccessTools.Method(_type, nameof(GameLocation.isWaterTile), new[] { typeof(int), typeof(int) }), prefix: new HarmonyMethod(GetType(), nameof(IsWaterTilePrefix)));
harmony.CreateReversePatcher(AccessTools.Method(_type, nameof(GameLocation.drawWater), new[] { typeof(SpriteBatch) }), new HarmonyMethod(GetType(), nameof(DrawWaterReversePatch))).Patch();

harmony.Patch(AccessTools.Method(_type, nameof(GameLocation.UpdateWhenCurrentLocation), new[] { typeof(GameTime) }), postfix: new HarmonyMethod(GetType(), nameof(UpdateWhenCurrentLocationPostfix)));
Expand Down Expand Up @@ -63,6 +64,17 @@ private static bool DrawWaterPrefix(GameLocation __instance, SpriteBatch b)
return true;
}

private static bool IsWaterTilePrefix(GameLocation __instance, int xTile, int yTile, ref bool __result)
{
if (DynamicReflections.isRenderingTopmostBackgroundWaterMask is false || !ReferenceEquals(__instance, Game1.currentLocation))
{
return true;
}

__result = LayerToolkit.IsTopmostVisibleBackgroundWater(__instance, xTile, yTile);
return false;
}

internal static void DrawWaterReversePatch(GameLocation __instance, SpriteBatch b)
{
new NotImplementedException("It's a stub!");
Expand Down
134 changes: 97 additions & 37 deletions DynamicReflections/Framework/Patches/xTile/LayerPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,11 @@
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Locations;
using StardewValley.Menus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Reflection;
using xTile.Dimensions;
using xTile.Display;
using xTile.Layers;
using xTile.Tiles;
using Object = StardewValley.Object;

namespace DynamicReflections.Framework.Patches.Tiles
{
Expand All @@ -31,41 +24,57 @@ internal LayerPatch(IMonitor modMonitor, IModHelper modHelper) : base(modMonitor

internal void Apply(Harmony harmony)
{
harmony.Patch(AccessTools.Method(_object, "DrawNormal", new[] { typeof(IDisplayDevice), typeof(xTile.Dimensions.Rectangle), typeof(xTile.Dimensions.Location), typeof(int), typeof(float) }), prefix: new HarmonyMethod(GetType(), nameof(DrawNormalPrefix)));
harmony.Patch(AccessTools.Method(_object, "DrawNormal", new[] { typeof(IDisplayDevice), typeof(xTile.Dimensions.Rectangle), typeof(xTile.Dimensions.Location), typeof(int), typeof(float) }), postfix: new HarmonyMethod(GetType(), nameof(DrawNormalPostfix)));
MethodInfo drawNormal = AccessTools.Method(_object, "DrawNormal", new[]
{
typeof(IDisplayDevice),
typeof(xTile.Dimensions.Rectangle),
typeof(xTile.Dimensions.Location),
typeof(int),
typeof(float)
});

harmony.Patch(drawNormal, prefix: new HarmonyMethod(GetType(), nameof(DrawNormalPrefix)));
harmony.Patch(drawNormal, postfix: new HarmonyMethod(GetType(), nameof(DrawNormalPostfix)));
harmony.CreateReversePatcher(drawNormal, new HarmonyMethod(GetType(), nameof(DrawNormalReversePatch))).Patch();

PatchPyTkLayerDraw(harmony, "Platonymous.Toolkit", "PyTK.Extensions.PyMaps, PyTK", "PyTK");
PatchPyTkLayerDraw(harmony, "Platonymous.TMXLoader", "TMXLoader.PyMaps, TMXLoader", "TMXLoader");
}

harmony.CreateReversePatcher(AccessTools.Method(_object, "DrawNormal", new[] { typeof(IDisplayDevice), typeof(xTile.Dimensions.Rectangle), typeof(xTile.Dimensions.Location), typeof(int), typeof(float) }), new HarmonyMethod(GetType(), nameof(DrawNormalReversePatch))).Patch();
private void PatchPyTkLayerDraw(Harmony harmony, string modId, string typeName, string displayName)
{
if (!DynamicReflections.modHelper.ModRegistry.IsLoaded(modId))
{
return;
}

// Perform PyTK related patches
if (DynamicReflections.modHelper.ModRegistry.IsLoaded("Platonymous.Toolkit"))
try
{
try
Type pyTkType = Type.GetType(typeName);
if (pyTkType is null)
{
if (Type.GetType("PyTK.Extensions.PyMaps, PyTK") is Type PyTK && PyTK != null)
{
harmony.Patch(AccessTools.Method(PyTK, "drawLayer", new[] { typeof(Layer), typeof(IDisplayDevice), typeof(xTile.Dimensions.Rectangle), typeof(int), typeof(Location), typeof(bool) }), prefix: new HarmonyMethod(GetType(), nameof(PyTKDrawLayerPrefix)));
}
return;
}
catch (Exception ex)

MethodInfo drawLayer = AccessTools.Method(pyTkType, "drawLayer", new[]
{
_monitor.Log($"Failed to patch PyTK in {this.GetType().Name}: DR may not properly display reflections!", LogLevel.Warn);
_monitor.Log($"Patch for PyTK failed in {this.GetType().Name}: {ex}", LogLevel.Trace);
typeof(Layer),
typeof(IDisplayDevice),
typeof(xTile.Dimensions.Rectangle),
typeof(int),
typeof(Location),
typeof(bool)
});

if (drawLayer is not null)
{
harmony.Patch(drawLayer, prefix: new HarmonyMethod(GetType(), nameof(PyTKDrawLayerPrefix)));
}
}
if (DynamicReflections.modHelper.ModRegistry.IsLoaded("Platonymous.TMXLoader"))
catch (Exception ex)
{
try
{
if (Type.GetType("TMXLoader.PyMaps, TMXLoader") is Type PyTK && PyTK != null)
{
harmony.Patch(AccessTools.Method(PyTK, "drawLayer", new[] { typeof(Layer), typeof(IDisplayDevice), typeof(xTile.Dimensions.Rectangle), typeof(int), typeof(Location), typeof(bool) }), prefix: new HarmonyMethod(GetType(), nameof(PyTKDrawLayerPrefix)));
}
}
catch (Exception ex)
{
_monitor.Log($"Failed to patch TMXLoader in {this.GetType().Name}: DR may not properly display reflections!", LogLevel.Warn);
_monitor.Log($"Patch for TMXLoader failed in {this.GetType().Name}: {ex}", LogLevel.Trace);
}
_monitor.Log($"Failed to patch {displayName} in {GetType().Name}: DR may not properly display reflections!", LogLevel.Warn);
_monitor.Log($"Patch for {displayName} failed in {GetType().Name}: {ex}", LogLevel.Trace);
}
}

Expand All @@ -80,8 +89,13 @@ private static bool DrawNormalPrefix(Layer __instance, IDisplayDevice displayDev
DynamicReflections.isDrawingWaterReflection = false;
DynamicReflections.isDrawingMirrorReflection = false;

if (__instance.Equals(LayerToolkit.GetLowestBackgroundLayer(Game1.currentLocation)) is true)
var lowestBackgroundLayer = LayerToolkit.GetLowestBackgroundLayer(Game1.currentLocation);
bool hasMultipleBackgroundLayers = LayerToolkit.HasMultipleBackgroundLayers(Game1.currentLocation);

if (__instance.Equals(lowestBackgroundLayer) is true)
{
DynamicReflections.shouldDeferWaterReflectionPresentation = false;
DynamicReflections.shouldDeferSkyReflectionPresentation = false;
SpriteBatchToolkit.CacheSpriteBatchSettings(Game1.spriteBatch, endSpriteBatch: true);

// Pre-render the Mirrors layer (this should always be done, regardless of DynamicReflections.shouldDrawMirrorReflection)
Expand Down Expand Up @@ -134,8 +148,19 @@ private static bool DrawNormalPrefix(Layer __instance, IDisplayDevice displayDev

// Resume previous SpriteBatch
SpriteBatchToolkit.ResumeCachedSpriteBatch(Game1.spriteBatch);
if (DynamicReflections.isFilteringWater is false && DynamicReflections.isFilteringSky is false)
if (DynamicReflections.isFilteringWater is false && DynamicReflections.shouldDrawNightSky is false)
{
return true;
}

// Keep the original single-Back behavior intact.
// Only multi-Back* maps defer the final water/sky presentation until the top of the background stack.
if (hasMultipleBackgroundLayers is true)
{
DynamicReflections.shouldDeferSkyReflectionPresentation = DynamicReflections.shouldDrawNightSky;
DynamicReflections.shouldDeferWaterReflectionPresentation = DynamicReflections.isFilteringWater;
DynamicReflections.isFilteringSky = false;
DynamicReflections.isFilteringWater = false;
return true;
}

Expand Down Expand Up @@ -204,7 +229,11 @@ private static void DrawNormalPostfix(Layer __instance, IDisplayDevice displayDe
return;
}

if (__instance.Equals(LayerToolkit.GetLowestBackgroundLayer(Game1.currentLocation)) is true)
var lowestBackgroundLayer = LayerToolkit.GetLowestBackgroundLayer(Game1.currentLocation);
var highestBackgroundLayer = LayerToolkit.GetHighestBackgroundLayer(Game1.currentLocation);
bool hasMultipleBackgroundLayers = LayerToolkit.HasMultipleBackgroundLayers(Game1.currentLocation);

if (__instance.Equals(lowestBackgroundLayer) is true)
{
if (DynamicReflections.isDrawingPuddles is true)
{
Expand All @@ -223,6 +252,37 @@ private static void DrawNormalPostfix(Layer __instance, IDisplayDevice displayDe

}
}

// Present the already-rendered water/sky reflections after the highest background layer.
// The explicit water mask keeps the original placement behavior on maps that use Back* layer stacks.
if (hasMultipleBackgroundLayers is true && __instance.Equals(highestBackgroundLayer) is true && (DynamicReflections.shouldDeferWaterReflectionPresentation is true || DynamicReflections.shouldDeferSkyReflectionPresentation is true))
{
SpriteBatchToolkit.CacheSpriteBatchSettings(Game1.spriteBatch, endSpriteBatch: true);

SpriteBatchToolkit.RenderTopmostBackgroundWaterMask();

if (DynamicReflections.shouldDeferSkyReflectionPresentation is true)
{
SpriteBatchToolkit.DrawMaskedNightSky();

if (DynamicReflections.shouldDeferWaterReflectionPresentation is true)
{
DynamicReflections.shouldDeferWaterReflectionPresentation = false;
DynamicReflections.isDrawingWaterReflection = true;
}

DynamicReflections.shouldDeferSkyReflectionPresentation = false;
}
else if (DynamicReflections.shouldDeferWaterReflectionPresentation is true)
{
SpriteBatchToolkit.DrawMaskedRenderedCharacters(isWavy: DynamicReflections.currentWaterSettings.IsReflectionWavy);

DynamicReflections.shouldDeferWaterReflectionPresentation = false;
DynamicReflections.isDrawingWaterReflection = true;
}

SpriteBatchToolkit.ResumeCachedSpriteBatch(Game1.spriteBatch);
}
}

internal static void DrawNormalReversePatch(Layer __instance, IDisplayDevice displayDevice, xTile.Dimensions.Rectangle mapViewport, Location displayOffset, int pixelZoom, float sort_offset = 0f)
Expand Down
Loading