From 491e572a122b56a01719e8b346265ca78ff38a8f Mon Sep 17 00:00:00 2001 From: jneb802 <73610029+jneb802@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:48:01 -0700 Subject: [PATCH 1/4] fix: sync ZoneLocation radii/ClearArea from prefab's Location component LocationConfig.ExteriorRadius, InteriorRadius, and ClearArea are now nullable. GetZoneLocation falls back to the previous defaults (10f/0f/false) when null so existing callers are unaffected. CustomLocation now syncs these three fields from the prefab's Location component onto the ZoneLocation whenever the corresponding config field is unset (null). Applied in both constructors: - Non-SoftReference ctor: sync runs immediately after GetZoneLocation, using the component that was authored on (or added to) the prefab. - SoftReference ctor: sync runs inside OnLocationResolve once the prefab asset is actually loaded. The LocationConfig reference is stored so the callback can check which fields the caller left null. Before this change, ZoneLocation.m_exteriorRadius (used by world-gen terrain-delta sampling and spawn-time ClearArea/slope sampling) could diverge from Location.m_exteriorRadius authored in Unity Editor, leading to placements that failed terrain checks at a radius smaller than the authored footprint and vegetation clearing that stopped short. Semantics: config wins when set; otherwise prefab component wins; otherwise the original defaults apply. Co-Authored-By: Claude Opus 4.6 (1M context) --- JotunnLib/Configs/LocationConfig.cs | 19 +++++++++++-------- JotunnLib/Entities/CustomLocation.cs | 28 +++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/JotunnLib/Configs/LocationConfig.cs b/JotunnLib/Configs/LocationConfig.cs index f03dfc860..0a9616d35 100644 --- a/JotunnLib/Configs/LocationConfig.cs +++ b/JotunnLib/Configs/LocationConfig.cs @@ -44,8 +44,9 @@ public class LocationConfig /// /// Radius of the location. Terrain delta is calculated within this circle. + /// If null, falls back to the prefab's component value when one exists. /// - public float ExteriorRadius { get; set; } = 10f; + public float? ExteriorRadius { get; set; } /// /// Attempt to place in the central zone first @@ -129,9 +130,10 @@ public class LocationConfig public bool HasInterior { get; set; } /// - /// Radius of the interior attached to the location + /// Radius of the interior attached to the location. + /// If null, falls back to the prefab's component value when one exists. /// - public float InteriorRadius { get; set; } + public float? InteriorRadius { get; set; } /// /// Environment string used by the interior @@ -159,9 +161,10 @@ public class LocationConfig public bool IconAlways { get; set; } /// - /// Enable to forbid Vegetation from spawning inside the circle defined by + /// Enable to forbid Vegetation from spawning inside the circle defined by . + /// If null, falls back to the prefab's component value when one exists. /// - public bool ClearArea { get; set; } + public bool? ClearArea { get; set; } /// /// Create a new @@ -218,9 +221,9 @@ public ZoneSystem.ZoneLocation GetZoneLocation() m_biomeArea = BiomeArea, m_quantity = Quantity, m_prioritized = Priotized, - m_interiorRadius = InteriorRadius, - m_exteriorRadius = ExteriorRadius, - m_clearArea = ClearArea, + m_interiorRadius = InteriorRadius ?? 0f, + m_exteriorRadius = ExteriorRadius ?? 10f, + m_clearArea = ClearArea ?? false, m_centerFirst = CenterFirst, m_forestTresholdMin = ForestTresholdMin, m_forestTresholdMax = ForestTrasholdMax, diff --git a/JotunnLib/Entities/CustomLocation.cs b/JotunnLib/Entities/CustomLocation.cs index 7f21a9fce..d3f1c47e1 100644 --- a/JotunnLib/Entities/CustomLocation.cs +++ b/JotunnLib/Entities/CustomLocation.cs @@ -95,11 +95,11 @@ public CustomLocation(GameObject exteriorPrefab, GameObject interiorPrefab, bool else { Location = exteriorPrefab.AddComponent(); - Location.m_clearArea = locationConfig.ClearArea; - Location.m_exteriorRadius = locationConfig.ExteriorRadius; + if (locationConfig.ClearArea.HasValue) Location.m_clearArea = locationConfig.ClearArea.Value; + if (locationConfig.ExteriorRadius.HasValue) Location.m_exteriorRadius = locationConfig.ExteriorRadius.Value; Location.m_interiorPrefab = interiorPrefab; Location.m_hasInterior = locationConfig.HasInterior; - Location.m_interiorRadius = locationConfig.InteriorRadius; + if (locationConfig.InteriorRadius.HasValue) Location.m_interiorRadius = locationConfig.InteriorRadius.Value; Location.m_interiorEnvironment = locationConfig.InteriorEnvironment; } @@ -107,6 +107,8 @@ public CustomLocation(GameObject exteriorPrefab, GameObject interiorPrefab, bool ZoneLocation.m_prefab = new SoftReference(AssetManager.Instance.AddAsset(exteriorPrefab)); ZoneLocation.m_prefabName = exteriorPrefab.name; + SyncZoneLocationFromComponent(Location, locationConfig); + FixReference = fixReference; } @@ -124,6 +126,7 @@ public CustomLocation(SoftReference softReferencePrefab, bool fixRef return; } + _locationConfig = locationConfig; var parent = ZoneManager.Instance.LocationContainer.transform; AssetManager.Instance.ResolveMocksOnLoad(softReferencePrefab, parent, OnLocationResolve); Name = softReferencePrefab.Name; @@ -134,14 +137,33 @@ public CustomLocation(SoftReference softReferencePrefab, bool fixRef SoftReference = true; } + private readonly LocationConfig _locationConfig; + private void OnLocationResolve(GameObject gameObject) { + if (gameObject.TryGetComponent(out var location)) + { + SyncZoneLocationFromComponent(location, _locationConfig); + } + if (gameObject.TryGetComponent(out var zoneLocation)) { ZoneManager.Instance.PrepareLocation(zoneLocation, SourceMod); } } + private void SyncZoneLocationFromComponent(Location location, LocationConfig locationConfig) + { + if (location == null || ZoneLocation == null) + { + return; + } + + if (!locationConfig.ExteriorRadius.HasValue) ZoneLocation.m_exteriorRadius = location.m_exteriorRadius; + if (!locationConfig.InteriorRadius.HasValue) ZoneLocation.m_interiorRadius = location.m_interiorRadius; + if (!locationConfig.ClearArea.HasValue) ZoneLocation.m_clearArea = location.m_clearArea; + } + /// /// Helper method to determine if a location prefab with a given name is a custom location created with Jötunn. /// From e17cb523eb843bed12088b677ba8af9c7e51a637 Mon Sep 17 00:00:00 2001 From: jneb802 <73610029+jneb802@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:05:14 -0700 Subject: [PATCH 2/4] preserve LocationConfig binary compat with internal Has* flags --- JotunnLib/Configs/LocationConfig.cs | 52 +++++++++++++++++++++++----- JotunnLib/Entities/CustomLocation.cs | 12 +++---- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/JotunnLib/Configs/LocationConfig.cs b/JotunnLib/Configs/LocationConfig.cs index 0a9616d35..1f2e3aa39 100644 --- a/JotunnLib/Configs/LocationConfig.cs +++ b/JotunnLib/Configs/LocationConfig.cs @@ -42,11 +42,23 @@ public class LocationConfig [Obsolete("This property is unused by Valheim.")] public float ChanceToSpawn { get; set; } = 10f; + private float? _exteriorRadius; + /// /// Radius of the location. Terrain delta is calculated within this circle. - /// If null, falls back to the prefab's component value when one exists. + /// If left unset, falls back to the prefab's component value when one exists. + /// Defaults to 10f when no value has been set and no component is present. /// - public float? ExteriorRadius { get; set; } + public float ExteriorRadius + { + get => _exteriorRadius ?? 10f; + set => _exteriorRadius = value; + } + + /// + /// True when the caller explicitly assigned a value to . + /// + internal bool HasExteriorRadius => _exteriorRadius.HasValue; /// /// Attempt to place in the central zone first @@ -129,11 +141,22 @@ public class LocationConfig /// public bool HasInterior { get; set; } + private float? _interiorRadius; + /// /// Radius of the interior attached to the location. - /// If null, falls back to the prefab's component value when one exists. + /// If left unset, falls back to the prefab's component value when one exists. + /// + public float InteriorRadius + { + get => _interiorRadius ?? 0f; + set => _interiorRadius = value; + } + + /// + /// True when the caller explicitly assigned a value to . /// - public float? InteriorRadius { get; set; } + internal bool HasInteriorRadius => _interiorRadius.HasValue; /// /// Environment string used by the interior @@ -160,11 +183,22 @@ public class LocationConfig /// public bool IconAlways { get; set; } + private bool? _clearArea; + /// /// Enable to forbid Vegetation from spawning inside the circle defined by . - /// If null, falls back to the prefab's component value when one exists. + /// If left unset, falls back to the prefab's component value when one exists. + /// + public bool ClearArea + { + get => _clearArea ?? false; + set => _clearArea = value; + } + + /// + /// True when the caller explicitly assigned a value to . /// - public bool? ClearArea { get; set; } + internal bool HasClearArea => _clearArea.HasValue; /// /// Create a new @@ -221,9 +255,9 @@ public ZoneSystem.ZoneLocation GetZoneLocation() m_biomeArea = BiomeArea, m_quantity = Quantity, m_prioritized = Priotized, - m_interiorRadius = InteriorRadius ?? 0f, - m_exteriorRadius = ExteriorRadius ?? 10f, - m_clearArea = ClearArea ?? false, + m_interiorRadius = InteriorRadius, + m_exteriorRadius = ExteriorRadius, + m_clearArea = ClearArea, m_centerFirst = CenterFirst, m_forestTresholdMin = ForestTresholdMin, m_forestTresholdMax = ForestTrasholdMax, diff --git a/JotunnLib/Entities/CustomLocation.cs b/JotunnLib/Entities/CustomLocation.cs index d3f1c47e1..1f911a65b 100644 --- a/JotunnLib/Entities/CustomLocation.cs +++ b/JotunnLib/Entities/CustomLocation.cs @@ -95,11 +95,11 @@ public CustomLocation(GameObject exteriorPrefab, GameObject interiorPrefab, bool else { Location = exteriorPrefab.AddComponent(); - if (locationConfig.ClearArea.HasValue) Location.m_clearArea = locationConfig.ClearArea.Value; - if (locationConfig.ExteriorRadius.HasValue) Location.m_exteriorRadius = locationConfig.ExteriorRadius.Value; + if (locationConfig.HasClearArea) Location.m_clearArea = locationConfig.ClearArea; + if (locationConfig.HasExteriorRadius) Location.m_exteriorRadius = locationConfig.ExteriorRadius; Location.m_interiorPrefab = interiorPrefab; Location.m_hasInterior = locationConfig.HasInterior; - if (locationConfig.InteriorRadius.HasValue) Location.m_interiorRadius = locationConfig.InteriorRadius.Value; + if (locationConfig.HasInteriorRadius) Location.m_interiorRadius = locationConfig.InteriorRadius; Location.m_interiorEnvironment = locationConfig.InteriorEnvironment; } @@ -159,9 +159,9 @@ private void SyncZoneLocationFromComponent(Location location, LocationConfig loc return; } - if (!locationConfig.ExteriorRadius.HasValue) ZoneLocation.m_exteriorRadius = location.m_exteriorRadius; - if (!locationConfig.InteriorRadius.HasValue) ZoneLocation.m_interiorRadius = location.m_interiorRadius; - if (!locationConfig.ClearArea.HasValue) ZoneLocation.m_clearArea = location.m_clearArea; + if (!locationConfig.HasExteriorRadius) ZoneLocation.m_exteriorRadius = location.m_exteriorRadius; + if (!locationConfig.HasInteriorRadius) ZoneLocation.m_interiorRadius = location.m_interiorRadius; + if (!locationConfig.HasClearArea) ZoneLocation.m_clearArea = location.m_clearArea; } /// From 69e32b30826e01149f8765a6aa91165a2da14596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schm=C3=B6cker?= Date: Sat, 9 May 2026 17:53:33 +0200 Subject: [PATCH 3/4] fix: set _locationConfig in all constructors --- JotunnLib/Entities/CustomLocation.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/JotunnLib/Entities/CustomLocation.cs b/JotunnLib/Entities/CustomLocation.cs index 1f911a65b..e155df7e8 100644 --- a/JotunnLib/Entities/CustomLocation.cs +++ b/JotunnLib/Entities/CustomLocation.cs @@ -15,6 +15,8 @@ namespace Jotunn.Entities /// public class CustomLocation : CustomEntity { + private readonly LocationConfig _locationConfig; + /// /// The exterior prefab for this custom location. /// @@ -87,6 +89,7 @@ public CustomLocation(GameObject exteriorPrefab, GameObject interiorPrefab, bool { Prefab = exteriorPrefab; Name = exteriorPrefab.name; + _locationConfig = locationConfig; if (exteriorPrefab.TryGetComponent(out var location)) { @@ -137,8 +140,6 @@ public CustomLocation(SoftReference softReferencePrefab, bool fixRef SoftReference = true; } - private readonly LocationConfig _locationConfig; - private void OnLocationResolve(GameObject gameObject) { if (gameObject.TryGetComponent(out var location)) From b360eebf26f4c4a8816db574033abfadfa0fad1c Mon Sep 17 00:00:00 2001 From: jneb802 <73610029+jneb802@users.noreply.github.com> Date: Mon, 11 May 2026 09:17:59 -0700 Subject: [PATCH 4/4] fix: keep LocationConfig fallbacks for new Location components --- JotunnLib/Entities/CustomLocation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/JotunnLib/Entities/CustomLocation.cs b/JotunnLib/Entities/CustomLocation.cs index e155df7e8..ede21adc9 100644 --- a/JotunnLib/Entities/CustomLocation.cs +++ b/JotunnLib/Entities/CustomLocation.cs @@ -98,11 +98,11 @@ public CustomLocation(GameObject exteriorPrefab, GameObject interiorPrefab, bool else { Location = exteriorPrefab.AddComponent(); - if (locationConfig.HasClearArea) Location.m_clearArea = locationConfig.ClearArea; - if (locationConfig.HasExteriorRadius) Location.m_exteriorRadius = locationConfig.ExteriorRadius; + Location.m_clearArea = locationConfig.ClearArea; + Location.m_exteriorRadius = locationConfig.ExteriorRadius; Location.m_interiorPrefab = interiorPrefab; Location.m_hasInterior = locationConfig.HasInterior; - if (locationConfig.HasInteriorRadius) Location.m_interiorRadius = locationConfig.InteriorRadius; + Location.m_interiorRadius = locationConfig.InteriorRadius; Location.m_interiorEnvironment = locationConfig.InteriorEnvironment; }