diff --git a/DynamicReflections/DynamicReflections.cs b/DynamicReflections/DynamicReflections.cs index 120f241..be395e4 100644 --- a/DynamicReflections/DynamicReflections.cs +++ b/DynamicReflections/DynamicReflections.cs @@ -1,4 +1,4 @@ -using HarmonyLib; +using HarmonyLib; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI; @@ -47,7 +47,7 @@ public class DynamicReflections : Mod // Water reflection variables internal static Dictionary npcToWaterReflectionPosition = new Dictionary(); - internal static Vector2? waterReflectionPosition; + internal static readonly Dictionary waterTileCache = new(); internal static Vector2? waterReflectionPosition; internal static Vector2? waterReflectionTilePosition; internal static bool shouldDrawWaterReflection; internal static bool isDrawingWaterReflection; @@ -304,146 +304,281 @@ private void OnUpdateTicked(object sender, StardewModdingAPI.Events.UpdateTicked DynamicReflections.shouldDrawWaterReflection = false; if (modConfig.AreWaterReflectionsEnabled is not false && currentWaterSettings is not null && currentWaterSettings.AreReflectionsEnabled) { - var positionInverter = currentWaterSettings.ReflectionDirection == Direction.North && currentWaterSettings.PlayerReflectionOffset.Y > 0 ? -1 : 1; - var playerPosition = Game1.player.Position; - playerPosition += currentWaterSettings.PlayerReflectionOffset * 64 * positionInverter; - DynamicReflections.waterReflectionPosition = playerPosition; - DynamicReflections.waterReflectionTilePosition = playerPosition / 64f; + // Calculate the player's reflection position based on offset and direction + var playerOffset = currentWaterSettings.PlayerReflectionOffset; - // Hide the reflection if it will show up out of bounds on the map or not drawn on water tile - var waterReflectionPosition = DynamicReflections.waterReflectionTilePosition.Value; - for (int yOffset = -1; yOffset <= Math.Ceiling(currentWaterSettings.PlayerReflectionOffset.Y); yOffset++) - { - var tilePosition = waterReflectionPosition + new Vector2(0, yOffset); - if (IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X - 1, (int)tilePosition.Y) is true || IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X, (int)tilePosition.Y) is true || IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X + 1, (int)tilePosition.Y) is true) - { - DynamicReflections.shouldDrawWaterReflection = true; - break; - } - } + var positionInverter = currentWaterSettings.ReflectionDirection == Direction.North && playerOffset.Y > 0 + ? -1 + : 1; - // Handle the wavy effect if enabled - if (currentWaterSettings.IsReflectionWavy && DynamicReflections.waterReflectionEffect is not null) - { - var phase = waterReflectionEffect.Parameters["Phase"].GetValueSingle(); - phase += (float)Game1.currentGameTime.ElapsedGameTime.TotalSeconds * currentWaterSettings.WaveSpeed; + var playerPosition = Game1.player.Position; + playerPosition += playerOffset * 64f * positionInverter; - waterReflectionEffect.Parameters["Phase"].SetValue(phase); - waterReflectionEffect.Parameters["Frequency"].SetValue(currentWaterSettings.WaveFrequency); - waterReflectionEffect.Parameters["Amplitude"].SetValue(currentWaterSettings.WaveAmplitude); - } - } + DynamicReflections.waterReflectionPosition = playerPosition; + DynamicReflections.waterReflectionTilePosition = playerPosition / 64f; + + // Hide the reflection if it will show up out of bounds on the map or not drawn on a water tile + var waterReflectionPosition = DynamicReflections.waterReflectionTilePosition.Value; + for (int yOffset = -1; yOffset <= Math.Ceiling(playerOffset.Y); yOffset++) + { + var tilePosition = waterReflectionPosition + new Vector2(0, yOffset); + + if (IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X, (int)tilePosition.Y) is true + || IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X - 1, (int)tilePosition.Y) is true + || IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X + 1, (int)tilePosition.Y) is true) + { + DynamicReflections.shouldDrawWaterReflection = true; + break; + } + } + + // Handle the wavy effect if enabled + if (currentWaterSettings.IsReflectionWavy && DynamicReflections.waterReflectionEffect is not null) + { + var phase = waterReflectionEffect.Parameters["Phase"].GetValueSingle(); + phase += (float)Game1.currentGameTime.ElapsedGameTime.TotalSeconds * currentWaterSettings.WaveSpeed; - if (modConfig.AreNPCReflectionsEnabled is not false && currentWaterSettings is not null && currentWaterSettings.AreReflectionsEnabled) + waterReflectionEffect.Parameters["Phase"].SetValue(phase); + waterReflectionEffect.Parameters["Frequency"].SetValue(currentWaterSettings.WaveFrequency); + waterReflectionEffect.Parameters["Amplitude"].SetValue(currentWaterSettings.WaveAmplitude); + } + +} + + if (currentWaterSettings is not null && currentWaterSettings.AreReflectionsEnabled + && (modConfig.AreNPCReflectionsEnabled is not false || modConfig.AreCompanionReflectionsEnabled)) { - npcToWaterReflectionPosition.Clear(); - if (Game1.currentLocation is not null && Game1.currentLocation.characters is not null) + bool npcThrottlingEnabled = DynamicReflections.modConfig.Performance?.EnableNpcThrottling ?? false; + int npcInterval = Math.Max(1, DynamicReflections.modConfig.Performance?.NpcUpdateIntervalTicks ?? 1); + + bool companionThrottlingEnabled = DynamicReflections.modConfig.Performance?.EnableCompanionThrottling ?? false; + int companionInterval = Math.Max(1, DynamicReflections.modConfig.Performance?.CompanionUpdateIntervalTicks ?? 1); + + bool shouldUpdateNpcs = modConfig.AreNPCReflectionsEnabled && (!npcThrottlingEnabled || e.IsMultipleOf((uint)npcInterval)); + bool shouldUpdateCompanions = modConfig.AreCompanionReflectionsEnabled && (!companionThrottlingEnabled || e.IsMultipleOf((uint)companionInterval)); + + if (shouldUpdateNpcs || shouldUpdateCompanions) { - foreach (var npc in GetActiveNPCs(Game1.currentLocation)) + var location = Game1.currentLocation; + var characters = location?.characters; + + if (location is not null && characters is not null) { - var positionInverter = currentWaterSettings.ReflectionDirection == Direction.North && currentWaterSettings.NPCReflectionOffset.Y > 0 ? -1 : 1; - var npcPosition = npc.Position; - npcPosition += currentWaterSettings.NPCReflectionOffset * 64 * positionInverter; + // Clear outdated entries in npcToWaterReflectionPosition + if (npcToWaterReflectionPosition.Count > 0) + { + if (shouldUpdateNpcs && shouldUpdateCompanions) + { + npcToWaterReflectionPosition.Clear(); + } + else + { + var keys = npcToWaterReflectionPosition.Keys.ToList(); + foreach (var key in keys) + { + bool keyIsCompanion = IsCustomCompanion(key); + + // Remove NPC entries when updating NPCs, + // remove companion entries when updating companions. + if ((shouldUpdateNpcs && !keyIsCompanion) || + (shouldUpdateCompanions && keyIsCompanion)) + { + npcToWaterReflectionPosition.Remove(key); + } + } + } + } + + int npcCount = 0; + int companionCount = 0; - // Hide the reflection if it will show up out of bounds on the map or not drawn on water tile - var waterReflectionPosition = npcPosition / 64f; - for (int yOffset = -1; yOffset <= Math.Ceiling(currentWaterSettings.NPCReflectionOffset.Y); yOffset++) + var performance = DynamicReflections.modConfig.Performance; + int maxNpcReflections = performance?.MaxNpcReflections ?? int.MaxValue; + int maxCompanionReflections = performance?.MaxCompanionReflections ?? int.MaxValue; + + // Rebuild NPC / companion reflection entries + foreach (var npc in GetActiveNPCs(location)) { - var tilePosition = waterReflectionPosition + new Vector2(0, yOffset); - if (IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X - 1, (int)tilePosition.Y) is true || IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X, (int)tilePosition.Y) is true || IsWaterReflectiveTile(Game1.currentLocation, (int)tilePosition.X + 1, (int)tilePosition.Y) is true) + bool isCompanion = IsCustomCompanion(npc); + + if (isCompanion) { - npcToWaterReflectionPosition[npc] = npcPosition; - break; + if (!modConfig.AreCompanionReflectionsEnabled || !shouldUpdateCompanions) + continue; + if (companionCount >= maxCompanionReflections) + continue; + } + else + { + if (!modConfig.AreNPCReflectionsEnabled || !shouldUpdateNpcs) + continue; + if (npcCount >= maxNpcReflections) + continue; + } + + var npcOffset = isCompanion + ? currentWaterSettings.CompanionReflectionOffset + : currentWaterSettings.NPCReflectionOffset; + + var positionInverter = + currentWaterSettings.ReflectionDirection == Direction.North && + npcOffset.Y > 0 + ? -1 + : 1; + + var npcPosition = npc.Position; + npcPosition += npcOffset * 64 * positionInverter; + + // Hide the reflection if it will show up out of bounds on the map + // or not drawn on water tiles + var waterReflectionPosition = npcPosition / 64f; + for (int yOffset = -1; yOffset <= Math.Ceiling(npcOffset.Y); yOffset++) + { + var tilePosition = waterReflectionPosition + new Vector2(0, yOffset); + + if (IsWaterReflectiveTile(location, (int)tilePosition.X - 1, (int)tilePosition.Y) is true + || IsWaterReflectiveTile(location, (int)tilePosition.X + 1, (int)tilePosition.Y) is true) + { + npcToWaterReflectionPosition[npc] = npcPosition; + + if (isCompanion) + companionCount++; + else + npcCount++; + + break; + } } } } } } - // Handle the mirror reflections + // MIRROR REFLECTIONS (Optimized / Supports Performance Throttling) DynamicReflections.shouldDrawMirrorReflection = false; + if (DynamicReflections.modConfig.AreMirrorReflectionsEnabled) { - var playerWorldPosition = Game1.player.Position; - var playerTilePosition = Game1.player.TilePoint; - - DynamicReflections.activeMirrorPositions.Clear(); - foreach (var mirror in DynamicReflections.mirrors.Values.OrderByDescending(m => m.TilePosition.Y)) + // If no mirrors exist in this location, fast exit + if (DynamicReflections.mirrors.Count == 0) + { + DynamicReflections.activeMirrorPositions.Clear(); + } + else { - mirror.IsEnabled = false; + var performance = DynamicReflections.modConfig.Performance; + bool mirrorThrottlingEnabled = performance?.EnableMirrorThrottling ?? false; + int mirrorInterval = Math.Max(1, performance?.MirrorUpdateIntervalTicks ?? 1); - // Limit the amount of active Mirrors to the amount of available reflection renders - if (activeMirrorPositions.Count >= DynamicReflections.maskedPlayerMirrorReflectionRenders.Length) - { - break; - } + bool shouldRecalculateMirrors = + !mirrorThrottlingEnabled + || e.IsMultipleOf((uint)mirrorInterval); - var mirrorWidth = mirror.TilePosition.X + (mirror.FurnitureLink is not null ? (int)Math.Ceiling(mirror.Settings.Dimensions.Width / 16f) : mirror.Settings.Dimensions.Width); - if (mirror.TilePosition.X - 1 <= playerTilePosition.X && playerTilePosition.X <= mirrorWidth) + if (shouldRecalculateMirrors) { - var mirrorRange = mirror.TilePosition.Y + (mirror.FurnitureLink is not null ? (int)Math.Ceiling(mirror.Settings.Dimensions.Height / 16f) : mirror.Settings.Dimensions.Height); - if (mirror.TilePosition.Y < playerTilePosition.Y && playerTilePosition.Y <= mirrorRange) + var playerWorldPosition = Game1.player.Position; + var playerTilePosition = Game1.player.TilePoint; + + DynamicReflections.activeMirrorPositions.Clear(); + + foreach (var mirror in DynamicReflections.mirrors.Values.OrderByDescending(m => m.TilePosition.Y)) { - // Skip any mirrors that are within range of an already active mirror - if (IsTileWithinActiveMirror(mirrorRange)) + mirror.IsEnabled = false; + + if (DynamicReflections.maskedPlayerMirrorReflectionRenders == null || + DynamicReflections.maskedPlayerMirrorReflectionRenders.Length == 0) { - continue; + break; } - mirror.IsEnabled = true; - mirror.ActiveIndex = DynamicReflections.activeMirrorPositions.Count; + if (DynamicReflections.activeMirrorPositions.Count >= DynamicReflections.maskedPlayerMirrorReflectionRenders.Length) + { + break; + } - var playerDistanceFromBase = mirror.WorldPosition.Y - playerWorldPosition.Y; - var adjustedPosition = new Vector2(playerWorldPosition.X, mirror.WorldPosition.Y + playerDistanceFromBase + 64f); - mirror.PlayerReflectionPosition = adjustedPosition; + var mirrorWidth = mirror.TilePosition.X + + (mirror.FurnitureLink != null + ? (int)Math.Ceiling(mirror.Settings.Dimensions.Width / 16f) + : mirror.Settings.Dimensions.Width); - DynamicReflections.shouldDrawMirrorReflection = true; - DynamicReflections.activeMirrorPositions.Add(mirror.TilePosition); + if (mirror.TilePosition.X - 1 <= playerTilePosition.X && + playerTilePosition.X <= mirrorWidth) + { + var mirrorRange = mirror.TilePosition.Y + + (mirror.FurnitureLink != null + ? (int)Math.Ceiling(mirror.Settings.Dimensions.Height / 16f) + : mirror.Settings.Dimensions.Height); + + if (mirror.TilePosition.Y < playerTilePosition.Y && + playerTilePosition.Y <= mirrorRange) + { + if (IsTileWithinActiveMirror(mirrorRange)) + continue; + + mirror.IsEnabled = true; + mirror.ActiveIndex = DynamicReflections.activeMirrorPositions.Count; + + var playerDistanceFromBase = mirror.WorldPosition.Y - playerWorldPosition.Y; + var adjustedPosition = new Vector2( + playerWorldPosition.X, + mirror.WorldPosition.Y + playerDistanceFromBase + 64f); + + mirror.PlayerReflectionPosition = adjustedPosition; + + DynamicReflections.activeMirrorPositions.Add(mirror.TilePosition); + } + } } } - } - if (DynamicReflections.mirrorReflectionSprite is null) - { - DynamicReflections.mirrorReflectionSprite = new FarmerSprite(Game1.player.FarmerSprite.textureName.Value); - } + DynamicReflections.shouldDrawMirrorReflection = + DynamicReflections.activeMirrorPositions.Count > 0; - if (Game1.player.FacingDirection == 0 && DynamicReflections.mirrorReflectionSprite.PauseForSingleAnimation is false && Game1.player.UsingTool is false) - { - bool isCarrying = Game1.player.IsCarrying(); - if (Game1.player.isMoving()) + if (DynamicReflections.shouldDrawMirrorReflection) { - if (Game1.player.running && !isCarrying) + if (DynamicReflections.mirrorReflectionSprite == null) { - DynamicReflections.mirrorReflectionSprite.animate(32, Game1.currentGameTime); + DynamicReflections.mirrorReflectionSprite = + new FarmerSprite(Game1.player.FarmerSprite.textureName.Value); } - else if (Game1.player.running) - { - DynamicReflections.mirrorReflectionSprite.animate(128, Game1.currentGameTime); - } - else if (isCarrying) - { - DynamicReflections.mirrorReflectionSprite.animate(96, Game1.currentGameTime); - } - else + + if (Game1.player.FacingDirection == 0 && + DynamicReflections.mirrorReflectionSprite.PauseForSingleAnimation == false && + Game1.player.UsingTool == false) { - DynamicReflections.mirrorReflectionSprite.animate(0, Game1.currentGameTime); + bool isCarrying = Game1.player.IsCarrying(); + + if (Game1.player.isMoving()) + { + if (Game1.player.running && !isCarrying) + DynamicReflections.mirrorReflectionSprite.animate(32, Game1.currentGameTime); + else if (Game1.player.running) + DynamicReflections.mirrorReflectionSprite.animate(128, Game1.currentGameTime); + else if (isCarrying) + DynamicReflections.mirrorReflectionSprite.animate(96, Game1.currentGameTime); + else + DynamicReflections.mirrorReflectionSprite.animate(0, Game1.currentGameTime); + } + else if (isCarrying) + { + DynamicReflections.mirrorReflectionSprite.setCurrentFrame(128); + } + else + { + DynamicReflections.mirrorReflectionSprite.setCurrentFrame(32); + } } } - else if (Game1.player.IsCarrying()) - { - DynamicReflections.mirrorReflectionSprite.setCurrentFrame(128); - } - else - { - DynamicReflections.mirrorReflectionSprite.setCurrentFrame(32); - } } } } private void OnDayStarted(object sender, StardewModdingAPI.Events.DayStartedEventArgs e) { + + // Clear cached water tiles for the new day (maps may change / reload) + DynamicReflections.waterTileCache.Clear(); + // Populate the location-based settings GMCMHelper.RefreshLocationListing(); @@ -483,9 +618,6 @@ private void OnGameLaunched(object sender, StardewModdingAPI.Events.GameLaunched GMCMHelper.Register(apiManager.GetGenericModConfigMenuApi(), this); } - // Load in our shaders - // Compile via the command: mgfxc wavy.fx wavy.mgfx - // Unused: opacityEffect = new Effect(Game1.graphics.GraphicsDevice, File.ReadAllBytes(Path.Combine(modHelper.DirectoryPath, "Framework", "Assets", "Shaders", "opacity.mgfx"))); mirrorReflectionEffect = new Effect(Game1.graphics.GraphicsDevice, File.ReadAllBytes(Path.Combine(modHelper.DirectoryPath, "Framework", "Assets", "Shaders", "mask.mgfx"))); waterReflectionEffect = new Effect(Game1.graphics.GraphicsDevice, File.ReadAllBytes(Path.Combine(modHelper.DirectoryPath, "Framework", "Assets", "Shaders", "wavy.mgfx"))); @@ -500,6 +632,10 @@ private void LoadContentPacks(bool silent = false) // Clear the existing cache of custom buildings mirrorsManager.Reset(); + // Clear water cache when content packs reload (maps may change) + DynamicReflections.waterTileCache.Clear(); + + // Load owned content packs foreach (IContentPack contentPack in Helper.ContentPacks.GetOwned()) { @@ -747,6 +883,22 @@ internal void SetPuddleReflectionSettings(bool recalculate = false) } } + if (map.Properties.ContainsKey(PuddleSettings.MapProperty_CompanionReflectionOffset)) + { + try + { + if (JsonSerializer.Deserialize(map.Properties[PuddleSettings.MapProperty_CompanionReflectionOffset]) is Vector2 offset) + { + currentPuddleSettings.CompanionReflectionOffset = offset; + } + } + catch (Exception ex) + { + Monitor.Log($"Failed to get PuddleSettings.MapProperty_CompanionReflectionOffset from the map {map.Id}!", LogLevel.Warn); + Monitor.Log($"Failed to get PuddleSettings.MapProperty_CompanionReflectionOffset from the map {map.Id}: {ex}", LogLevel.Trace); + } + } + if (map.Properties.ContainsKey(PuddleSettings.MapProperty_PuddlePercentageWhileRaining)) { if (Int32.TryParse(map.Properties[PuddleSettings.MapProperty_PuddlePercentageWhileRaining], out var percentage)) @@ -910,6 +1062,22 @@ internal void SetWaterReflectionSettings() } } + if (map.Properties.ContainsKey(WaterSettings.MapProperty_CompanionReflectionOffset)) + { + try + { + if (JsonSerializer.Deserialize(map.Properties[WaterSettings.MapProperty_CompanionReflectionOffset]) is Vector2 offset) + { + currentWaterSettings.CompanionReflectionOffset = offset; + } + } + catch (Exception ex) + { + Monitor.Log($"Failed to get WaterSettings.MapProperty_CompanionReflectionOffset from the map {map.Id}!", LogLevel.Warn); + Monitor.Log($"Failed to get WaterSettings.MapProperty_CompanionReflectionOffset from the map {map.Id}: {ex}", LogLevel.Trace); + } + } + if (map.Properties.ContainsKey(WaterSettings.MapProperty_IsReflectionWavy)) { currentWaterSettings.IsReflectionWavy = map.Properties[WaterSettings.MapProperty_IsReflectionWavy].ToString().Equals("T", StringComparison.OrdinalIgnoreCase); @@ -1202,13 +1370,63 @@ private Vector2 GetMirrorOffset(GameLocation location, int x, int y) private bool IsWaterReflectiveTile(GameLocation location, int x, int y) { if (location is null) - { return false; + + // Quick reject for nonsense coordinates + if (x < 0 || y < 0) + return false; + + var performance = DynamicReflections.modConfig?.Performance; + bool useCache = performance?.EnableSafeCaching == true; + + // If Safe Caching is off, behave exactly as before. + if (!useCache) + { + return location.isWaterTile(x, y); } - return location.isWaterTile(x, y); + var map = location.Map; + if (map is null || map.Layers is null || map.Layers.Count == 0) + { + return location.isWaterTile(x, y); + } + + // Prefer the Back layer; fall back to first layer if needed just in case. + var backLayer = map.GetLayer("Back") ?? map.Layers[0]; + int width = backLayer.LayerWidth; + int height = backLayer.LayerHeight; + + // Look up or build the cache for this *location*. + if (!waterTileCache.TryGetValue(location, out bool[,] cache) + || cache is null + || cache.GetLength(0) != width + || cache.GetLength(1) != height) + { + cache = new bool[width, height]; + + // Build using the game's own isWaterTile logic, once per tile. + for (int tileX = 0; tileX < width; tileX++) + { + for (int tileY = 0; tileY < height; tileY++) + { + cache[tileX, tileY] = location.isWaterTile(tileX, tileY); + } + } + + waterTileCache[location] = cache; + } + + // Out of bounds relative to this map + if (x >= cache.GetLength(0) || y >= cache.GetLength(1)) + return false; + + return cache[x, y]; } + + + + internal static int GetReflectedDirection(int initialDirection, bool isMirror = false) { if (initialDirection == 0) @@ -1223,6 +1441,35 @@ internal static int GetReflectedDirection(int initialDirection, bool isMirror = return initialDirection; } + internal static bool IsCustomCompanion(NPC npc) + { + if (npc is null) + { + return false; + } + + var type = npc.GetType(); + if (type is null) + { + return false; + } + + string fullName = type.FullName ?? string.Empty; + + // Detect companions created by the Custom Companions framework (used by SH's Wild Animals and others). + if (fullName.StartsWith("CustomCompanions.", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (fullName.Contains(".Framework.Companions.", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + internal static List GetActiveNPCs(GameLocation location) { var npcs = new List(); diff --git a/DynamicReflections/DynamicReflections.csproj b/DynamicReflections/DynamicReflections.csproj index 56a8b0a..446586b 100644 --- a/DynamicReflections/DynamicReflections.csproj +++ b/DynamicReflections/DynamicReflections.csproj @@ -1,10 +1,10 @@ - + 3.0.0 net6.0 latest true - false + true false E:\SteamLibrary\steamapps\common\Stardew Valley\Mods @@ -68,24 +68,4 @@ Always - - - - $(PostBuildEventDependsOn); - PostBuildMacros; - - - powershell -Command "(ls *manifest.json -rec | foreach-object { $f=$_.FullName; (gc -LiteralPath \"$f\") -replace 'REPLACE_ME_WITH_VERSION', '$(Version)' | sc -LiteralPath \"$f\" })" - - powershell Remove-Item -Path 'C:\Users\Floogen\Documents\GitHub Repos\DynamicReflections\DynamicReflections\releases\latest\DynamicReflections"' -Recurse -Force - xcopy /s /y /i "C:\Users\Floogen\Documents\GitHub Repos\DynamicReflections\DynamicReflections\bin\Debug\$(TargetFramework)" "C:\Users\Floogen\Documents\GitHub Repos\DynamicReflections\DynamicReflections\releases\latest\DynamicReflections" - 7z a -tzip "C:\Users\Floogen\Documents\GitHub Repos\DynamicReflections\DynamicReflections\releases\DynamicReflections-$(Version).zip" "C:\Users\Floogen\Documents\GitHub Repos\DynamicReflections\DynamicReflections\releases\latest\DynamicReflections" - - powershell Remove-Item -Path '$(GameModsPath)\DynamicReflections' -Recurse -Force - powershell Remove-Item -Path '$(GameModsPath)\Dynamic Reflections Examples' -Recurse -Force - - xcopy /s /y /i "C:\Users\Floogen\Documents\GitHub Repos\DynamicReflections\DynamicReflections\releases\latest\DynamicReflections" "$(GameModsPath)\DynamicReflections" - xcopy /s /y /i "C:\Users\Floogen\Documents\GitHub Repos\DynamicReflections\DynamicReflections\Examples\*" "$(GameModsPath)\Dynamic Reflections Examples" - - diff --git a/DynamicReflections/Framework/External/GenericModConfigMenu/GMCMHelper.cs b/DynamicReflections/Framework/External/GenericModConfigMenu/GMCMHelper.cs index 613ca27..905f722 100644 --- a/DynamicReflections/Framework/External/GenericModConfigMenu/GMCMHelper.cs +++ b/DynamicReflections/Framework/External/GenericModConfigMenu/GMCMHelper.cs @@ -35,7 +35,8 @@ public static void Register(IGenericModConfigMenuApi configApi, DynamicReflectio configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.AreMirrorReflectionsEnabled, value => DynamicReflections.modConfig.AreMirrorReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.mirror_reflections")); configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.AreWaterReflectionsEnabled, value => DynamicReflections.modConfig.AreWaterReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.water_reflections")); configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.ArePuddleReflectionsEnabled, value => DynamicReflections.modConfig.ArePuddleReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.puddle_reflections")); - configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.AreNPCReflectionsEnabled, value => DynamicReflections.modConfig.AreNPCReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.npc_reflections")); + configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.AreNPCReflectionsEnabled, value => DynamicReflections.modConfig.AreNPCReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.npc_reflections")); + configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.AreCompanionReflectionsEnabled, value => DynamicReflections.modConfig.AreCompanionReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.companion_reflections")); configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.AreSkyReflectionsEnabled, value => DynamicReflections.modConfig.AreSkyReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.sky_reflections")); configApi.AddKeybind(ModManifest, () => DynamicReflections.modConfig.QuickMenuKey, value => DynamicReflections.modConfig.QuickMenuKey = value, () => Helper.Translation.Get("config.general_settings.shortcut_key"), () => Helper.Translation.Get("config.general_settings.shortcut_key.description")); @@ -58,13 +59,16 @@ public static void Register(IGenericModConfigMenuApi configApi, DynamicReflectio configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].AreReflectionsEnabled, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].AreReflectionsEnabled = value, () => Helper.Translation.Get("config.general_settings.water_reflections")); configApi.AddTextOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].ReflectionDirection.ToString(), value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].ReflectionDirection = (Direction)Enum.Parse(typeof(Direction), value), () => Helper.Translation.Get("config.water_settings.reflection_direction"), tooltip: () => Helper.Translation.Get("config.water_settings.reflection_direction.description"), new string[] { Direction.North.ToString(), Direction.South.ToString() }); - configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.reflection_offets")); + configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.reflection_offsets")); configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.player_offsets")); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].PlayerReflectionOffset.X, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].PlayerReflectionOffset = new Vector2(value, DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].PlayerReflectionOffset.Y), () => Helper.Translation.Get("config.water_settings.offset.x"), interval: 0.1f); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].PlayerReflectionOffset.Y, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].PlayerReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].PlayerReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.npc_offsets")); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset.X, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset = new Vector2(value, DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset.Y), () => Helper.Translation.Get("config.water_settings.offset.x"), interval: 0.1f); - configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset.Y, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset.Y, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].NPCReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); + configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.animals_offsets")); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].CompanionReflectionOffset.X, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].CompanionReflectionOffset = new Vector2(value, DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].CompanionReflectionOffset.Y), () => Helper.Translation.Get("config.water_settings.offset.x"), interval: 0.1f); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].CompanionReflectionOffset.Y, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].CompanionReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].CompanionReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.effects")); configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].IsReflectionWavy, value => DynamicReflections.modConfig.LocalWaterReflectionSettings[_currentLocation].IsReflectionWavy = value, () => Helper.Translation.Get("config.water_settings.is_wavy")); @@ -92,13 +96,16 @@ public static void Register(IGenericModConfigMenuApi configApi, DynamicReflectio configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ShouldPlaySplashSound, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ShouldPlaySplashSound = value, () => Helper.Translation.Get("config.puddle_settings.should_play_splash_sound")); configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ShouldRainSplashPuddles, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ShouldRainSplashPuddles = value, () => Helper.Translation.Get("config.puddle_settings.should_rain_splash_puddles")); - configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.reflection_offets")); + configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.reflection_offsets")); configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.player_offsets")); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ReflectionOffset.X, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ReflectionOffset = new Vector2(value, DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ReflectionOffset.Y), () => Helper.Translation.Get("config.water_settings.offset.x"), interval: 0.1f); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ReflectionOffset.Y, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].ReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.npc_offsets")); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset.X, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset = new Vector2(value, DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset.Y), () => Helper.Translation.Get("config.water_settings.offset.x"), interval: 0.1f); - configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset.Y, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset.Y, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].NPCReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); + configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.animals_offsets")); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].CompanionReflectionOffset.X, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].CompanionReflectionOffset = new Vector2(value, DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].CompanionReflectionOffset.Y), () => Helper.Translation.Get("config.water_settings.offset.x"), interval: 0.1f); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].CompanionReflectionOffset.Y, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].CompanionReflectionOffset = new Vector2(DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].CompanionReflectionOffset.X, value), () => Helper.Translation.Get("config.water_settings.offset.y"), interval: 0.1f); configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.general_settings.effects")); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].PuddlePercentageWhileRaining, value => DynamicReflections.modConfig.LocalPuddleReflectionSettings[_currentLocation].PuddlePercentageWhileRaining = value, () => Helper.Translation.Get("config.puddle_settings.puddle_percentage_while_raining"), min: 0, max: 100, interval: 1); @@ -157,7 +164,30 @@ public static void Register(IGenericModConfigMenuApi configApi, DynamicReflectio configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.sky_settings.star_reflections.title")); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].GettingDarkWaterAlpha, value => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].GettingDarkWaterAlpha = value, () => Helper.Translation.Get("config.sky_settings.water_alpha.getting_dark"), tooltip: () => Helper.Translation.Get("config.sky_settings.water_alpha.description"), min: 0.01f, max: 1f, interval: 0.01f); configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].HalfwayDarkWaterAlpha, value => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].HalfwayDarkWaterAlpha = value, () => Helper.Translation.Get("config.sky_settings.water_alpha.halfway_dark"), tooltip: () => Helper.Translation.Get("config.sky_settings.water_alpha.description"), min: 0.01f, max: 1f, interval: 0.01f); - configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].FinishedDarkWaterAlpha, value => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].FinishedDarkWaterAlpha = value, () => Helper.Translation.Get("config.sky_settings.water_alpha.finished_dark"), tooltip: () => Helper.Translation.Get("config.sky_settings.water_alpha.description"), min: 0.01f, max: 1f, interval: 0.01f); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].FinishedDarkWaterAlpha, value => DynamicReflections.modConfig.LocalSkyReflectionSettings[_currentLocation].FinishedDarkWaterAlpha = value, () => Helper.Translation.Get("config.sky_settings.water_alpha.finished_dark"), tooltip: () => Helper.Translation.Get("config.sky_settings.water_alpha.description"), min: 0.01f, max: 1f, interval: 0.01f); + + // Performance settings page + configApi.AddPage(ModManifest, String.Empty, () => Helper.Translation.Get("config.general_settings.title")); + configApi.AddPageLink(ModManifest, "performance_settings", () => Helper.Translation.Get("config.performance_settings.link")); + configApi.AddPage(ModManifest, "performance_settings", () => Helper.Translation.Get("config.performance_settings.title")); + + // Performance: caching + configApi.AddSectionTitle(ModManifest, () => Helper.Translation.Get("config.performance_settings.title")); + configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.Performance.EnableSafeCaching, value => DynamicReflections.modConfig.Performance.EnableSafeCaching = value, () => Helper.Translation.Get("config.performance_settings.enable_safe_caching")); + + // Performance: NPC reflections + configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.Performance.EnableNpcThrottling, value => DynamicReflections.modConfig.Performance.EnableNpcThrottling = value, () => Helper.Translation.Get("config.performance_settings.enable_npc_throttling")); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.Performance.NpcUpdateIntervalTicks, value => DynamicReflections.modConfig.Performance.NpcUpdateIntervalTicks = value, () => Helper.Translation.Get("config.performance_settings.npc_update_interval_ticks"), min: 1, max: 60, interval: 1); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.Performance.MaxNpcReflections, value => DynamicReflections.modConfig.Performance.MaxNpcReflections = value, () => Helper.Translation.Get("config.performance_settings.max_npc_reflections"), min: 1, max: 500, interval: 1); + + // Performance: mirrors + configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.Performance.EnableMirrorThrottling, value => DynamicReflections.modConfig.Performance.EnableMirrorThrottling = value, () => Helper.Translation.Get("config.performance_settings.enable_mirror_throttling")); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.Performance.MirrorUpdateIntervalTicks, value => DynamicReflections.modConfig.Performance.MirrorUpdateIntervalTicks = value, () => Helper.Translation.Get("config.performance_settings.mirror_update_interval_ticks"), min: 1, max: 60, interval: 1); + + // Performance: companions / wild animals + configApi.AddBoolOption(ModManifest, () => DynamicReflections.modConfig.Performance.EnableCompanionThrottling, value => DynamicReflections.modConfig.Performance.EnableCompanionThrottling = value, () => Helper.Translation.Get("config.performance_settings.enable_companion_throttling")); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.Performance.CompanionUpdateIntervalTicks, value => DynamicReflections.modConfig.Performance.CompanionUpdateIntervalTicks = value, () => Helper.Translation.Get("config.performance_settings.companion_update_interval_ticks"), min: 1, max: 60, interval: 1); + configApi.AddNumberOption(ModManifest, () => DynamicReflections.modConfig.Performance.MaxCompanionReflections, value => DynamicReflections.modConfig.Performance.MaxCompanionReflections = value, () => Helper.Translation.Get("config.performance_settings.max_companion_reflections"), min: 1, max: 500, interval: 1); configApi.AddPageLink(ModManifest, String.Empty, () => Helper.Translation.Get("config.general_settings.link.return_main")); } diff --git a/DynamicReflections/Framework/External/GenericModConfigMenu/ModConfig.cs b/DynamicReflections/Framework/External/GenericModConfigMenu/ModConfig.cs index d681759..54d7317 100644 --- a/DynamicReflections/Framework/External/GenericModConfigMenu/ModConfig.cs +++ b/DynamicReflections/Framework/External/GenericModConfigMenu/ModConfig.cs @@ -14,18 +14,20 @@ public class ModConfig public bool AreWaterReflectionsEnabled { get; set; } = true; public bool AreMirrorReflectionsEnabled { get; set; } = true; public bool ArePuddleReflectionsEnabled { get; set; } = true; - public bool AreNPCReflectionsEnabled { get; set; } = true; + public bool AreNPCReflectionsEnabled { get; set; } = true; + public bool AreCompanionReflectionsEnabled { get; set; } = true; public bool AreSkyReflectionsEnabled { get; set; } = true; public WaterSettings WaterReflectionSettings { get; set; } = new WaterSettings(); public PuddleSettings PuddleReflectionSettings { get; set; } = new PuddleSettings(); - public SkySettings SkyReflectionSettings { get; set; } = new SkySettings(); + public SkySettings SkyReflectionSettings { get; set; } = new SkySettings(); + public PerformanceSettings Performance { get; set; } = new PerformanceSettings(); public int MeteorShowerNightChance { get; set; } = 10; public Dictionary LocalWaterReflectionSettings { get; set; } = new Dictionary(); public Dictionary LocalPuddleReflectionSettings { get; set; } = new Dictionary(); public Dictionary LocalSkyReflectionSettings { get; set; } = new Dictionary(); - public SButton QuickMenuKey { get; set; } = SButton.R; + public SButton QuickMenuKey { get; set; } = SButton.None; public WaterSettings GetCurrentWaterSettings(GameLocation location) { diff --git a/DynamicReflections/Framework/Models/Settings/PerformanceSettings.cs b/DynamicReflections/Framework/Models/Settings/PerformanceSettings.cs new file mode 100644 index 0000000..c5ec951 --- /dev/null +++ b/DynamicReflections/Framework/Models/Settings/PerformanceSettings.cs @@ -0,0 +1,55 @@ +using System; + +namespace DynamicReflections.Framework.Models.Settings +{ + public class PerformanceSettings + { + /// + /// If true, some internal calculations (like water / puddle / mirror checks) can be cached + /// to reduce CPU usage. + /// + public bool EnableSafeCaching { get; set; } = false; + + /// + /// If true, NPC reflections can be updated less often and capped to reduce CPU usage. + /// + public bool EnableNpcThrottling { get; set; } = false; + + /// + /// How often to update NPC reflections, in ticks. 1 = every tick, 2 = every other tick, etc. + /// + public int NpcUpdateIntervalTicks { get; set; } = 2; + + /// + /// Maximum number of NPC reflections processed per location. + /// + public int MaxNpcReflections { get; set; } = 100; + + /// + /// If true, checks for which mirrors are active can be throttled. + /// + public bool EnableMirrorThrottling { get; set; } = false; + + /// + /// How often to re-evaluate which mirrors are active, in ticks. + /// + public int MirrorUpdateIntervalTicks { get; set; } = 2; + + /// + /// If true, reflections for companions / wild animals can be throttled separately + /// from normal NPCs. + /// + public bool EnableCompanionThrottling { get; set; } = false; + + /// + /// How often to update reflections for companions / wild animals, in ticks. + /// 1 = every tick. + /// + public int CompanionUpdateIntervalTicks { get; set; } = 2; + + /// + /// Maximum number of reflections for companions / wild animals to process per location. + /// + public int MaxCompanionReflections { get; set; } = 100; + } +} diff --git a/DynamicReflections/Framework/Models/Settings/PuddleSettings.cs b/DynamicReflections/Framework/Models/Settings/PuddleSettings.cs index 6ff48bf..0f4c6f1 100644 --- a/DynamicReflections/Framework/Models/Settings/PuddleSettings.cs +++ b/DynamicReflections/Framework/Models/Settings/PuddleSettings.cs @@ -27,6 +27,8 @@ public class PuddleSettings public Vector2 ReflectionOffset { get; set; } = Vector2.Zero; public const string MapProperty_NPCReflectionOffset = "PuddleNPCReflectionOffset"; public Vector2 NPCReflectionOffset { get; set; } = new Vector2(0f, 0.3f); + public const string MapProperty_CompanionReflectionOffset = "PuddleCompanionReflectionOffset"; + public Vector2 CompanionReflectionOffset { get; set; } = new Vector2(0f, 0.3f); public const string MapProperty_PuddlePercentageWhileRaining = "PuddlePercentageWhileRaining"; public int PuddlePercentageWhileRaining { get; set; } = 20; @@ -62,6 +64,7 @@ public void Reset(PuddleSettings referencedSettings = null) ShouldRainSplashPuddles = true; ReflectionOffset = Vector2.Zero; NPCReflectionOffset = new Vector2(0f, 0.3f); + CompanionReflectionOffset = new Vector2(0f, 0.3f); PuddlePercentageWhileRaining = 20; PuddlePercentageAfterRaining = 10; BigPuddleChance = 25; @@ -79,6 +82,7 @@ public void Reset(PuddleSettings referencedSettings = null) ShouldRainSplashPuddles = referencedSettings.ShouldRainSplashPuddles; ReflectionOffset = referencedSettings.ReflectionOffset; NPCReflectionOffset = referencedSettings.NPCReflectionOffset; + CompanionReflectionOffset = referencedSettings.CompanionReflectionOffset; PuddlePercentageWhileRaining = referencedSettings.PuddlePercentageWhileRaining; PuddlePercentageAfterRaining = referencedSettings.PuddlePercentageAfterRaining; BigPuddleChance = referencedSettings.BigPuddleChance; @@ -89,4 +93,4 @@ public void Reset(PuddleSettings referencedSettings = null) } } } -} +} \ No newline at end of file diff --git a/DynamicReflections/Framework/Models/Settings/SkySettings.cs b/DynamicReflections/Framework/Models/Settings/SkySettings.cs index e35ef2d..2032192 100644 --- a/DynamicReflections/Framework/Models/Settings/SkySettings.cs +++ b/DynamicReflections/Framework/Models/Settings/SkySettings.cs @@ -49,11 +49,11 @@ public class SkySettings public int MillisecondsBetweenShootingStarAttemptDuringMeteorShower { get; set; } = 250; public const string MapProperty_GettingDarkWaterAlpha = "StartingDarkWaterAlpha"; - public float GettingDarkWaterAlpha { get; set; } = 0.35f; + public float GettingDarkWaterAlpha { get; set; } = 0.38f; public const string MapProperty_HalfwayDarkWaterAlpha = "HalfwayDarkWaterAlpha"; - public float HalfwayDarkWaterAlpha { get; set; } = 0.075f; + public float HalfwayDarkWaterAlpha { get; set; } = 0.17f; public const string MapProperty_FinishedDarkWaterAlpha = "FinishedDarkWaterAlpha"; - public float FinishedDarkWaterAlpha { get; set; } = 0.005f; + public float FinishedDarkWaterAlpha { get; set; } = 0.04f; public bool OverrideDefaultSettings { get; set; } @@ -75,9 +75,9 @@ public void Reset(SkySettings referencedSettings = null) CometMinSpeed = 0.04f; CometMaxSpeed = 0.5f; MillisecondsBetweenShootingStarAttemptDuringMeteorShower = 250; - GettingDarkWaterAlpha = 0.35f; - HalfwayDarkWaterAlpha = 0.075f; - FinishedDarkWaterAlpha = 0.005f; + GettingDarkWaterAlpha = 0.38f; + HalfwayDarkWaterAlpha = 0.17f; + FinishedDarkWaterAlpha = 0.04f; OverrideDefaultSettings = false; } else diff --git a/DynamicReflections/Framework/Models/Settings/WaterSettings.cs b/DynamicReflections/Framework/Models/Settings/WaterSettings.cs index 1402879..d8b25c3 100644 --- a/DynamicReflections/Framework/Models/Settings/WaterSettings.cs +++ b/DynamicReflections/Framework/Models/Settings/WaterSettings.cs @@ -29,21 +29,23 @@ public class WaterSettings public Color ReflectionOverlay { get; set; } = Color.White; public const string MapProperty_ReflectionOffset = "WaterReflectionOffset"; - public Vector2 PlayerReflectionOffset { get; set; } = new Vector2(0f, 1.5f); + public Vector2 PlayerReflectionOffset { get; set; } = new Vector2(0f, 0.5f); public const string MapProperty_NPCReflectionOffset = "WaterNPCReflectionOffset"; - public Vector2 NPCReflectionOffset { get; set; } = new Vector2(0f, 1.1f); + public Vector2 NPCReflectionOffset { get; set; } = new Vector2(0f, 0.7f); + public const string MapProperty_CompanionReflectionOffset = "WaterCompanionReflectionOffset"; + public Vector2 CompanionReflectionOffset { get; set; } = new Vector2(0f, 0.3f); public const string MapProperty_IsReflectionWavy = "IsWaterReflectionWavy"; - public bool IsReflectionWavy { get; set; } = false; + public bool IsReflectionWavy { get; set; } = true; public const string MapProperty_WaveSpeed = "WaterReflectionWaveSpeed"; - public float WaveSpeed { get; set; } = 1f; + public float WaveSpeed { get; set; } = 2f; public const string MapProperty_WaveAmplitude = "WaterReflectionWaveAmplitude"; - public float WaveAmplitude { get; set; } = 0.01f; + public float WaveAmplitude { get; set; } = 0.001f; public const string MapProperty_WaveFrequency = "WaterReflectionWaveFrequency"; - public float WaveFrequency { get; set; } = 50f; + public float WaveFrequency { get; set; } = 200f; public bool OverrideDefaultSettings { get; set; } @@ -55,12 +57,13 @@ public void Reset(WaterSettings referencedSettings = null) AreReflectionsEnabled = true; ReflectionDirection = Direction.South; ReflectionOverlay = Color.White; - PlayerReflectionOffset = new Vector2(0f, 1.5f); - NPCReflectionOffset = new Vector2(0f, 1.1f); - IsReflectionWavy = false; - WaveSpeed = 1f; - WaveAmplitude = 0.01f; - WaveFrequency = 50f; + PlayerReflectionOffset = new Vector2(0f, 0.5f); + NPCReflectionOffset = new Vector2(0f, 0.7f); + CompanionReflectionOffset = new Vector2(0f, 0.3f); + IsReflectionWavy = true; + WaveSpeed = 2f; + WaveAmplitude = 0.001f; + WaveFrequency = 200f; OverrideDefaultSettings = false; } else @@ -70,6 +73,7 @@ public void Reset(WaterSettings referencedSettings = null) ReflectionOverlay = referencedSettings.ReflectionOverlay; PlayerReflectionOffset = referencedSettings.PlayerReflectionOffset; NPCReflectionOffset = referencedSettings.NPCReflectionOffset; + CompanionReflectionOffset = referencedSettings.CompanionReflectionOffset; IsReflectionWavy = referencedSettings.IsReflectionWavy; WaveSpeed = referencedSettings.WaveSpeed; WaveAmplitude = referencedSettings.WaveAmplitude; @@ -91,4 +95,4 @@ public bool IsFacingCorrectDirection(int direction) return false; } } -} +} \ No newline at end of file diff --git a/DynamicReflections/Framework/Utilities/SpriteBatchToolkit.cs b/DynamicReflections/Framework/Utilities/SpriteBatchToolkit.cs index 2f55ea9..fb49f22 100644 --- a/DynamicReflections/Framework/Utilities/SpriteBatchToolkit.cs +++ b/DynamicReflections/Framework/Utilities/SpriteBatchToolkit.cs @@ -1,4 +1,4 @@ -using DynamicReflections.Framework.Patches.Tiles; +using DynamicReflections.Framework.Patches.Tiles; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI; @@ -167,15 +167,13 @@ internal static void RenderMirrorReflectionPlayerSprite() var oldDirection = Game1.player.FacingDirection; var oldSprite = Game1.player.FarmerSprite; + // Cache modData for Fashion Sense Dictionary modDataCache = new Dictionary(); foreach (var dataKey in Game1.player.modData.Keys) { modDataCache[dataKey] = Game1.player.modData[dataKey]; } - // Note: Current solution is to utilize RenderTarget2Ds as the player sprite is composed of many other sprites layered on top of each other - // This makes modifying it via shader difficult and even more so difficult with Fashion Sense (as the size of appearances are not bounded) - // Draw the raw and flattened player sprites int index = 0; foreach (var mirrorPosition in DynamicReflections.activeMirrorPositions) @@ -188,13 +186,25 @@ internal static void RenderMirrorReflectionPlayerSprite() // Draw the scene Game1.graphics.GraphicsDevice.Clear(Color.Transparent); - Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp); - var mirror = DynamicReflections.mirrors[mirrorPosition]; var offsetPosition = mirror.PlayerReflectionPosition; offsetPosition += mirror.Settings.ReflectionOffset * 16; - Game1.player.Position = offsetPosition; + // Compute translation to draw the player as if their Position were offsetPosition + var playerScreen = Game1.GlobalToLocal(Game1.viewport, Game1.player.Position); + var targetScreen = Game1.GlobalToLocal(Game1.viewport, offsetPosition); + var delta = targetScreen - playerScreen; + + Game1.spriteBatch.Begin( + SpriteSortMode.FrontToBack, + BlendState.AlphaBlend, + SamplerState.PointClamp, + depthStencilState: null, + rasterizerState: null, + effect: null, + transformMatrix: Matrix.CreateTranslation(delta.X, delta.Y, 0f) + ); + Game1.player.FacingDirection = DynamicReflections.GetReflectedDirection(oldDirection, true); Game1.player.FarmerSprite = oldDirection == 0 ? DynamicReflections.mirrorReflectionSprite : oldSprite; Game1.player.modData["FashionSense.Animation.FacingDirection"] = Game1.player.FacingDirection.ToString(); @@ -218,17 +228,32 @@ internal static void RenderMirrorReflectionPlayerSprite() Game1.player.FacingDirection = DynamicReflections.GetReflectedDirection(oldDirection, true); - // Determine if we should flip the sprite on the X-axis (if facing front or back) - var flipEffect = Game1.player.FacingDirection is (0 or 2) ? SpriteEffects.FlipHorizontally : SpriteEffects.None; + // Should flip the sprite on the X-axis (if facing front or back) + var flipEffect = Game1.player.FacingDirection is (0 or 2) + ? SpriteEffects.FlipHorizontally + : SpriteEffects.None; - // This variable (flipOffset) is required to re-adjust the flipped screen (as the player sprite may not be in the center) - var flipOffset = Game1.player.FacingDirection is (0 or 2) ? (Game1.viewport.Width * Game1.options.zoomLevel - Game1.GlobalToLocal(Game1.viewport, Game1.player.Position).X * 2) - 64 : 0f; + // Use the mirror's reflection position as the flip center (like before, but without changing Position) + float reflectCenterX = Game1.GlobalToLocal(Game1.viewport, offsetPosition).X; + var flipOffset = Game1.player.FacingDirection is (0 or 2) + ? (Game1.viewport.Width * Game1.options.zoomLevel - reflectCenterX * 2f) - 64f + : 0f; // TODO: Implement these for Mirror.ReflectionScale var scale = new Vector2(1f, 1f); var scaleOffset = Vector2.Zero; - Game1.spriteBatch.Draw(rawReflectionRender, new Vector2(-flipOffset, 0f), rawReflectionRender.Bounds, mirror.Settings.ReflectionOverlay, 0f, scaleOffset, scale, flipEffect, 1f); + Game1.spriteBatch.Draw( + rawReflectionRender, + new Vector2(-flipOffset, 0f), + rawReflectionRender.Bounds, + mirror.Settings.ReflectionOverlay, + 0f, + scaleOffset, + scale, + flipEffect, + 1f + ); Game1.spriteBatch.End(); @@ -251,9 +276,7 @@ internal static void RenderMirrorReflectionPlayerSprite() foreach (var furniture in Game1.currentLocation.furniture) { if (mirror.FurnitureLink != furniture) - { continue; - } DynamicReflections.isFilteringMirror = true; furniture.draw(Game1.spriteBatch, (int)furniture.TileLocation.X, (int)furniture.TileLocation.Y); @@ -288,6 +311,7 @@ internal static void RenderMirrorReflectionPlayerSprite() index++; } + // Restore player state Game1.player.Position = oldPosition; Game1.player.FacingDirection = oldDirection; Game1.player.FarmerSprite = oldSprite; @@ -301,6 +325,7 @@ internal static void RenderMirrorReflectionPlayerSprite() Game1.graphics.GraphicsDevice.Clear(Game1.bgColor); } + internal static void RenderWaterReflectionNightSky() { // Set the render target @@ -420,22 +445,86 @@ internal static void RenderPuddleReflectionNPCs() return; } + // If puddle reflections aren't configured / active, don't waste work. + if (DynamicReflections.currentPuddleSettings is null) + { + return; + } + + var config = DynamicReflections.modConfig; + + // If both NPC and companion reflections are disabled, skip entirely. + bool npcReflectionsEnabled = config?.AreNPCReflectionsEnabled ?? true; + bool companionReflectionsEnabled = config?.AreCompanionReflectionsEnabled ?? true; + if (!npcReflectionsEnabled && !companionReflectionsEnabled) + { + return; + } + // Set the render target SpriteBatchToolkit.StartRendering(DynamicReflections.npcPuddleReflectionRender); // Draw the scene Game1.graphics.GraphicsDevice.Clear(Color.Transparent); - foreach (var npc in Game1.currentLocation.characters) + int npcCount = 0; + int companionCount = 0; + + foreach (var npc in DynamicReflections.GetActiveNPCs(Game1.currentLocation)) { + bool isCompanion = DynamicReflections.IsCustomCompanion(npc); + + // Respect global toggles and performance caps + if (isCompanion) + { + if (!companionReflectionsEnabled) + continue; + + int maxCompanions = config?.Performance?.MaxCompanionReflections ?? int.MaxValue; + if (companionCount >= maxCompanions) + continue; + } + else + { + if (!npcReflectionsEnabled) + continue; + + int maxNpcs = config?.Performance?.MaxNpcReflections ?? int.MaxValue; + if (npcCount >= maxNpcs) + continue; + } + + var offset = isCompanion + ? DynamicReflections.currentPuddleSettings.CompanionReflectionOffset + : DynamicReflections.currentPuddleSettings.NPCReflectionOffset; + + // Get the NPC's on-screen position + var npcScreenPos = Game1.GlobalToLocal(Game1.viewport, npc.Position); + + // Mirror pivot: NPC's Y on screen + puddle offset + float pivotY = npcScreenPos.Y + (offset.Y * 64f); + + // Flip vertically around that pivot var scale = Matrix.CreateScale(1, -1, 1); - var position = Matrix.CreateTranslation(0, Game1.GlobalToLocal(Game1.viewport, npc.Position + DynamicReflections.currentPuddleSettings.NPCReflectionOffset * 64).Y * 2, 0); + var position = Matrix.CreateTranslation(0, pivotY * 2f, 0); - Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, rasterizerState: DynamicReflections.rasterizer, transformMatrix: scale * position); + Game1.spriteBatch.Begin( + SpriteSortMode.FrontToBack, + BlendState.AlphaBlend, + SamplerState.PointClamp, + DepthStencilState.None, + DynamicReflections.rasterizer, + transformMatrix: scale * position + ); npc.draw(Game1.spriteBatch); - Game1.spriteBatch.End(); + + + if (isCompanion) + companionCount++; + else + npcCount++; } // Drop the render target @@ -444,6 +533,7 @@ internal static void RenderPuddleReflectionNPCs() Game1.graphics.GraphicsDevice.Clear(Game1.bgColor); } + internal static void DrawPuddleReflection(Texture2D mask) { DynamicReflections.mirrorReflectionEffect.Parameters["Mask"].SetValue(mask); @@ -496,74 +586,117 @@ internal static void RenderPuddleReflectionPlayerSprite() // Draw the scene Game1.graphics.GraphicsDevice.Clear(Color.Transparent); - var oldPosition = Game1.player.Position; var oldDirection = Game1.player.FacingDirection; var oldSprite = Game1.player.FarmerSprite; - var scale = Matrix.CreateScale(1, -1, 1); - var position = Matrix.CreateTranslation(0, Game1.GlobalToLocal(Game1.viewport, oldPosition).Y * 2, 0); - - Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, rasterizerState: DynamicReflections.rasterizer, transformMatrix: scale * position); - - var targetPosition = Game1.player.Position; - targetPosition -= DynamicReflections.currentPuddleSettings.ReflectionOffset * 64f; - Game1.player.Position = targetPosition; + // Original world position + var oldPosition = Game1.player.Position; + // Where the reflection was previously drawn (world space) + var worldOffset = DynamicReflections.currentPuddleSettings.ReflectionOffset * 64f; + var targetWorld = oldPosition - worldOffset; + + // Convert both positions to screen space to build an equivalent translation + var playerScreen = Game1.GlobalToLocal(Game1.viewport, oldPosition); + var targetScreen = Game1.GlobalToLocal(Game1.viewport, targetWorld); + var delta = targetScreen - playerScreen; + + // Same vertical flip & pivot as before (across the player's original local Y) + var scale = Matrix.CreateScale(1f, -1f, 1f); + var pivot = Matrix.CreateTranslation(0f, playerScreen.Y * 2f, 0f); + + // Apply the offset as a pre-translation, then the original reflection matrix + var preTranslation = Matrix.CreateTranslation(delta.X, delta.Y, 0f); + var transform = preTranslation * scale * pivot; + + Game1.spriteBatch.Begin( + SpriteSortMode.FrontToBack, + BlendState.AlphaBlend, + SamplerState.PointClamp, + depthStencilState: null, + rasterizerState: DynamicReflections.rasterizer, + effect: null, + transformMatrix: transform + ); + + // Draw the player at their real position; transform handles reflection+offset Game1.player.draw(Game1.spriteBatch); - Game1.player.Position = oldPosition; Game1.player.FacingDirection = oldDirection; Game1.player.FarmerSprite = oldSprite; Game1.spriteBatch.End(); + // Draw puddle ripples on top, unchanged Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp); - foreach (var rippleSprite in DynamicReflections.puddleManager.puddleRippleSprites.ToList()) { rippleSprite.draw(Game1.spriteBatch); } - Game1.spriteBatch.End(); // Drop the render target SpriteBatchToolkit.StopRendering(); - Game1.graphics.GraphicsDevice.Clear(Game1.bgColor); } + internal static void DrawReflectionViaMatrix() { - var oldPosition = Game1.player.Position; + // Cache what we’re going to touch so we can restore it var oldDirection = Game1.player.FacingDirection; var oldSprite = Game1.player.FarmerSprite; - if (DynamicReflections.modConfig.GetCurrentWaterSettings(Game1.currentLocation).ReflectionDirection == Models.Settings.Direction.South) - { - var scale = Matrix.CreateScale(1, -1, 1); - var position = Matrix.CreateTranslation(0, Game1.GlobalToLocal(Game1.viewport, DynamicReflections.waterReflectionPosition.Value).Y * 2, 0); + var currentWaterSettings = DynamicReflections.modConfig.GetCurrentWaterSettings(Game1.currentLocation); - Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, rasterizerState: DynamicReflections.rasterizer, transformMatrix: scale * position); + // Always draw the *real* player, just flip the screen with a matrix. + if (currentWaterSettings.ReflectionDirection == Models.Settings.Direction.South) + { + // Flip vertically around the water line in screen space. + var scale = Matrix.CreateScale(1f, -1f, 1f); + + // Pivot at the water reflection line (already computed in world space, convert to screen). + float pivotY = Game1.GlobalToLocal(Game1.viewport, DynamicReflections.waterReflectionPosition.Value).Y; + var position = Matrix.CreateTranslation(0f, pivotY * 2f, 0f); + + Game1.spriteBatch.Begin( + SpriteSortMode.FrontToBack, + BlendState.AlphaBlend, + SamplerState.PointClamp, + DepthStencilState.None, + DynamicReflections.rasterizer, + transformMatrix: scale * position + ); } else { - Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp); + // Non-south directions keep using the original mirror-style logic. + Game1.spriteBatch.Begin( + SpriteSortMode.FrontToBack, + BlendState.AlphaBlend, + SamplerState.PointClamp + ); Game1.player.FacingDirection = DynamicReflections.GetReflectedDirection(oldDirection, true); - Game1.player.FarmerSprite = oldDirection == 0 ? DynamicReflections.mirrorReflectionSprite : oldSprite; - Game1.player.modData["FashionSense.Animation.FacingDirection"] = Game1.player.FacingDirection.ToString(); + Game1.player.FarmerSprite = oldDirection == 0 + ? DynamicReflections.mirrorReflectionSprite + : oldSprite; + + Game1.player.modData["FashionSense.Animation.FacingDirection"] = + Game1.player.FacingDirection.ToString(); } - Game1.player.Position = DynamicReflections.waterReflectionPosition.Value; + // IMPORTANT: No longer touch Game1.player.Position here. Game1.player.draw(Game1.spriteBatch); - Game1.player.Position = oldPosition; + // Restore what changed Game1.player.FacingDirection = oldDirection; Game1.player.FarmerSprite = oldSprite; Game1.spriteBatch.End(); } + internal static void DrawRenderedCharacters(bool isWavy = false) { if (DynamicReflections.shouldDrawWaterReflection is true) diff --git a/DynamicReflections/i18n/default.json b/DynamicReflections/i18n/default.json index 6d6f7a0..c2791d6 100644 --- a/DynamicReflections/i18n/default.json +++ b/DynamicReflections/i18n/default.json @@ -1,86 +1,101 @@ { - // Config - "config.general_settings.title": "General Settings", - "config.general_settings.water_reflections": "Enable Water Reflections", - "config.general_settings.mirror_reflections": "Enable Mirror Reflections", - "config.general_settings.puddle_reflections": "Enable Puddle Reflections", - "config.general_settings.npc_reflections": "Enable NPC Reflections", - "config.general_settings.sky_reflections": "Enable Sky Reflections", - "config.general_settings.link.click_here": "Click Here", - "config.general_settings.link.return_main": "Return to the Main Page", - "config.general_settings.override_default_settings": "Override Default Settings", - "config.general_settings.override_default_settings.description": "If enabled, this location will ignore the default settings. Ignored when used on the 'Default' location.", - "config.general_settings.shortcut_key": "Settings Shortcut Key", - "config.general_settings.shortcut_key.description": "When pressing the given key, this menu will appear for quick configurations.", - "config.general_settings.effects": "Effects", + // Config + "config.general_settings.title": "General Settings", + "config.general_settings.water_reflections": "Enable Water Reflections", + "config.general_settings.mirror_reflections": "Enable Mirror Reflections", + "config.general_settings.puddle_reflections": "Enable Puddle Reflections", + "config.general_settings.npc_reflections": "Enable NPC Reflections", + "config.general_settings.companion_reflections": "Enable Companion Reflections", + "config.general_settings.sky_reflections": "Enable Sky Reflections", + "config.general_settings.link.click_here": "Click Here", + "config.general_settings.link.return_main": "Return to the Main Page", + "config.general_settings.override_default_settings": "Override Default Settings", + "config.general_settings.override_default_settings.description": "If enabled, this location will ignore the default settings. Ignored when used on the 'Default' location.", + "config.general_settings.shortcut_key": "Settings Shortcut Key", + "config.general_settings.shortcut_key.description": "When pressing the given key, this menu will appear for quick configurations.", + "config.general_settings.effects": "Effects", - "config.general_settings.reflection_offets": "Reflection Settings", - "config.general_settings.player_offsets": "Player Reflection Offsets", - "config.general_settings.npc_offsets": "NPC Reflection Offsets", - "config.water_settings.offset.x": "X Offset", - "config.water_settings.offset.y": "Y Offset", + "config.general_settings.reflection_offsets": "Reflection Settings", + "config.general_settings.player_offsets": "Player Reflection Offsets", + "config.general_settings.npc_offsets": "NPC Reflection Offsets", + "config.general_settings.animals_offsets": "Companion Reflection Offsets", + "config.water_settings.offset.x": "X Offset", + "config.water_settings.offset.y": "Y Offset", - "config.location_specific.title": "Location Specific Settings", - "config.location_specific.selector": "Target Location", - "config.location_specific.description": "Use `Default` to modify the default settings for all location.", + "config.location_specific.title": "Location Specific Settings", + "config.location_specific.selector": "Target Location", + "config.location_specific.description": "Use `Default` to modify the default settings for all location.", - "config.sky_settings.title": "Sky Specific Settings", - "config.sky_settings.link": "Go to Sky Settings", - "config.sky_settings.shooting_stars_enabled": "Enable Shooting Stars", - "config.sky_settings.star_density": "Star Density Percentage", - "config.sky_settings.meteor_shower_chance": "Meteor Shower Chance", - "config.sky_settings.meteor_shower_chance.description": "This percentage is shared across all locations.", - "config.sky_settings.shooting_stars.title": "Shooting Stars Settings", - "config.sky_settings.max_shooting_stars_per_attempt": "Max Amount of Shooting Stars", - "config.sky_settings.max_shooting_stars_per_attempt.description": "The max amount of shooting stars to generate per interval.", - "config.sky_settings.comet_chance": "Comet Chance", - "config.sky_settings.milliseconds_between_stars": "Shooting Stars Cooldown", - "config.sky_settings.milliseconds_between_stars.description": "The milliseconds between shooting stars.", - "config.sky_settings.milliseconds_between_stars_during_meteor_shower": "Shooting Stars Cooldown (Meteor Shower)", - "config.sky_settings.shooting_star_speed.min": "Shooting Stars Speed (Min)", - "config.sky_settings.shooting_star_speed.max": "Shooting Stars Speed (Max)", - "config.sky_settings.comet.min": "Comet Speed (Min)", - "config.sky_settings.comet.max": "Comet Speed (Max)", - "config.sky_settings.comet_segment.min": "Min Comet Segments", - "config.sky_settings.comet_segment.max": "Max Comet Segments", - "config.sky_settings.star_reflections.title": "Star Reflection Settings", - "config.sky_settings.water_alpha.getting_dark": "Water Alpha (Starting Dark Out)", - "config.sky_settings.water_alpha.halfway_dark": "Water Alpha (Halfway Dark Out)", - "config.sky_settings.water_alpha.finished_dark": "Water Alpha (Finished Dark Out)", - "config.sky_settings.water_alpha.description": "Determines the transparency of the water's wave effect for each stage of the sun setting.", + "config.sky_settings.title": "Sky Specific Settings", + "config.sky_settings.link": "Go to Sky Settings", + "config.sky_settings.shooting_stars_enabled": "Enable Shooting Stars", + "config.sky_settings.star_density": "Star Density Percentage", + "config.sky_settings.meteor_shower_chance": "Meteor Shower Chance", + "config.sky_settings.meteor_shower_chance.description": "This percentage is shared across all locations.", + "config.sky_settings.shooting_stars.title": "Shooting Stars Settings", + "config.sky_settings.max_shooting_stars_per_attempt": "Max Amount of Shooting Stars", + "config.sky_settings.max_shooting_stars_per_attempt.description": "The max amount of shooting stars to generate per interval.", + "config.sky_settings.comet_chance": "Comet Chance", + "config.sky_settings.milliseconds_between_stars": "Shooting Stars Cooldown", + "config.sky_settings.milliseconds_between_stars.description": "The milliseconds between shooting stars.", + "config.sky_settings.milliseconds_between_stars_during_meteor_shower": "Shooting Stars Cooldown (Meteor Shower)", + "config.sky_settings.shooting_star_speed.min": "Shooting Stars Speed (Min)", + "config.sky_settings.shooting_star_speed.max": "Shooting Stars Speed (Max)", + "config.sky_settings.comet.min": "Comet Speed (Min)", + "config.sky_settings.comet.max": "Comet Speed (Max)", + "config.sky_settings.comet_segment.min": "Min Comet Segments", + "config.sky_settings.comet_segment.max": "Max Comet Segments", + "config.sky_settings.star_reflections.title": "Star Reflection Settings", + "config.sky_settings.water_alpha.getting_dark": "Water Alpha (Starting Dark Out)", + "config.sky_settings.water_alpha.halfway_dark": "Water Alpha (Halfway Dark Out)", + "config.sky_settings.water_alpha.finished_dark": "Water Alpha (Finished Dark Out)", + "config.sky_settings.water_alpha.description": "Determines the transparency of the water's wave effect for each stage of the sun setting.", - "config.water_settings.title": "Water Specific Settings", - "config.water_settings.link": "Go to Water Settings", - "config.water_settings.reflection_direction": "Reflection Direction", - "config.water_settings.reflection_direction.description": "0 for North reflections, 2 for South reflections.", - "config.water_settings.color.title": "Reflection Overlay Color", - "config.water_settings.color.r": "Reflection Overlay Color (R)", - "config.water_settings.color.g": "Reflection Overlay Color (G)", - "config.water_settings.color.b": "Reflection Overlay Color (B)", - "config.water_settings.color.a": "Reflection Overlay Color (A)", - "config.water_settings.is_wavy": "Enable Wavy Reflections", - "config.water_settings.wave_speed": "Wave Speed", - "config.water_settings.wave_amplitude": "Wave Amplitude", - "config.water_settings.wave_frequency": "Wave Frequency", + "config.water_settings.title": "Water Specific Settings", + "config.water_settings.link": "Go to Water Settings", + "config.water_settings.reflection_direction": "Reflection Direction", + "config.water_settings.reflection_direction.description": "0 for North reflections, 2 for South reflections.", + "config.water_settings.color.title": "Reflection Overlay Color", + "config.water_settings.color.r": "Reflection Overlay Color (R)", + "config.water_settings.color.g": "Reflection Overlay Color (G)", + "config.water_settings.color.b": "Reflection Overlay Color (B)", + "config.water_settings.color.a": "Reflection Overlay Color (A)", + "config.water_settings.is_wavy": "Enable Wavy Reflections", + "config.water_settings.wave_speed": "Wave Speed", + "config.water_settings.wave_amplitude": "Wave Amplitude", + "config.water_settings.wave_frequency": "Wave Frequency", + + "config.puddle_settings.title": "Puddle Specific Settings", + "config.puddle_settings.link": "Go to Puddle Settings", + "config.puddle_settings.should_generate_puddles": "Should Generate Puddles", + "config.puddle_settings.should_play_splash_sound": "Should Play Splash Sound", + "config.puddle_settings.should_rain_splash_puddles": "Should Rain Splash Puddles", + "config.puddle_settings.puddle_percentage_while_raining": "Puddle Percentage While Raining", + "config.puddle_settings.puddle_percentage_after_raining": "Puddle Percentage After Raining", + "config.puddle_settings.big_puddle_chance": "Big Puddle Chance", + "config.puddle_settings.milliseconds_between_raindrop_splashes": "Raindrop Splashes Cooldown", + "config.puddle_settings.milliseconds_between_raindrop_splashes_description": "The milliseconds between raindrop splashes.", + "config.puddle_settings.puddle_color.title": "Puddle Color", + "config.puddle_settings.puddle_color.r": "Puddle Color (R)", + "config.puddle_settings.puddle_color.g": "Puddle Color (G)", + "config.puddle_settings.puddle_color.b": "Puddle Color (B)", + "config.puddle_settings.puddle_color.a": "Puddle Color (A)", + "config.puddle_settings.ripple_color.title": "Ripple Color", + "config.puddle_settings.ripple_color.r": "Ripple Color (R)", + "config.puddle_settings.ripple_color.g": "Ripple Color (G)", + "config.puddle_settings.ripple_color.b": "Ripple Color (B)", + "config.puddle_settings.ripple_color.a": "Ripple Color (A)", + + "config.performance_settings.title": "Performance Settings", + "config.performance_settings.link": "Performance & Throttling", + "config.performance_settings.enable_safe_caching": "Enable Safe Caching", + "config.performance_settings.enable_npc_throttling": "Enable NPC Throttling", + "config.performance_settings.npc_update_interval_ticks": "NPC Update Interval (ticks)", + "config.performance_settings.max_npc_reflections": "Max NPC Reflections per Location", + "config.performance_settings.enable_mirror_throttling": "Enable Mirror Throttling", + "config.performance_settings.mirror_update_interval_ticks": "Mirror Update Interval (ticks)", + "config.performance_settings.enable_companion_throttling": "Enable Companion Throttling", + "config.performance_settings.companion_update_interval_ticks": "Companion Update Interval (ticks)", + "config.performance_settings.max_companion_reflections": "Max Companion Reflections per Location" - "config.puddle_settings.title": "Puddle Specific Settings", - "config.puddle_settings.link": "Go to Puddle Settings", - "config.puddle_settings.should_generate_puddles": "Should Generate Puddles", - "config.puddle_settings.should_play_splash_sound": "Should Play Splash Sound", - "config.puddle_settings.should_rain_splash_puddles": "Should Rain Splash Puddles", - "config.puddle_settings.puddle_percentage_while_raining": "Puddle Percentage While Raining", - "config.puddle_settings.puddle_percentage_after_raining": "Puddle Percentage After Raining", - "config.puddle_settings.big_puddle_chance": "Big Puddle Chance", - "config.puddle_settings.milliseconds_between_raindrop_splashes": "Raindrop Splashes Cooldown", - "config.puddle_settings.milliseconds_between_raindrop_splashes_description": "The milliseconds between raindrop splashes.", - "config.puddle_settings.puddle_color.title": "Puddle Color", - "config.puddle_settings.puddle_color.r": "Puddle Color (R)", - "config.puddle_settings.puddle_color.g": "Puddle Color (G)", - "config.puddle_settings.puddle_color.b": "Puddle Color (B)", - "config.puddle_settings.puddle_color.a": "Puddle Color (A)", - "config.puddle_settings.ripple_color.title": "Ripple Color", - "config.puddle_settings.ripple_color.r": "Ripple Color (R)", - "config.puddle_settings.ripple_color.g": "Ripple Color (G)", - "config.puddle_settings.ripple_color.b": "Ripple Color (B)", - "config.puddle_settings.ripple_color.a": "Ripple Color (A)" } \ No newline at end of file diff --git a/DynamicReflections/manifest.json b/DynamicReflections/manifest.json index e1d7350..6e32ff1 100644 --- a/DynamicReflections/manifest.json +++ b/DynamicReflections/manifest.json @@ -1,11 +1,13 @@ -{ +{ "Name": "Dynamic Reflections", "Author": "PeacefulEnd", - "Version": "REPLACE_ME_WITH_VERSION", + "Version": "4.0.0", "Description": "Dynamic water and surface reflections for Stardew Valley.", "UniqueID": "PeacefulEnd.DynamicReflections", "EntryDll": "DynamicReflections.dll", "MinimumApiVersion": "3.14.4", - "UpdateKeys": [ "Nexus:12831" ], + "UpdateKeys": [ + "Nexus:12831" + ], "Dependencies": [] -} \ No newline at end of file +}