Skip to content

fix: sync ZoneLocation radii/ClearArea from prefab's Location component#472

Merged
MSchmoecker merged 4 commits into
Valheim-Modding:devfrom
jneb802:fix/location-exterior-radius-from-prefab
May 12, 2026
Merged

fix: sync ZoneLocation radii/ClearArea from prefab's Location component#472
MSchmoecker merged 4 commits into
Valheim-Modding:devfrom
jneb802:fix/location-exterior-radius-from-prefab

Conversation

@jneb802
Copy link
Copy Markdown
Contributor

@jneb802 jneb802 commented Apr 13, 2026

The Issue

LocationConfig.ExteriorRadius, InteriorRadius, and ClearArea each exist in two independent places:

  1. The Location MonoBehaviour on the prefab.
  2. The ZoneSystem.ZoneLocation registration record.

CustomLocation sourced ZoneLocation.m_exteriorRadius exclusively from LocationConfig.ExteriorRadius (defaulting to 10f). The prefab's Location component was preserved when present but never read into the ZoneLocation, so the two values could silently diverge.

Where It Surfaced

The mismatch was caught via VentureValheim/LocationReset, which resets the contents of a location to their originally generated state. Its LocationReset.LocationPosition constructor reads the reset boundary from ZoneSystem.ZoneLocation:

public LocationPosition(LocationProxy loc, ZoneSystem.ZoneLocation zone, Location location)
{
    ...
    GroundDistance = zone.m_exteriorRadius;   // ZoneLocation, NOT the Location component
    SkyDistance    = zone.m_interiorRadius;
    ...
}

LocationReset calls TryResetAfterLoadPrefab, with zone fetched via ZoneSystem.instance.GetLocation(hash). For More World Locations AIO locations, zone.m_exteriorRadius carried the LocationConfig value while the radius set in UnityEditor was larger. Therefore, LocationReset only reset a subset of the location's actual footprint and left objects beyond the config radius unreset.

The Fix

LocationConfig

The three shared fields become nullable so Jötunn can distinguish "caller explicitly set this" from "caller left this unset." When a field is null, CustomLocation treats the prefab's Location component as the source of truth; when it has a value, that value wins.

  • GetZoneLocation() falls back to the prior defaults when unset, preserving behavior for prefabs without a Location component. Existing callers that assign a value compile unchanged via implicit conversion from float / bool to the nullable types.

CustomLocation

Synchronizes the three shared fields onto the ZoneLocation from the prefab's Location component whenever the corresponding config field is null. Config wins when set; otherwise the component's value flows through.

  • Non-SoftReference constructor: SyncZoneLocationFromComponent() runs immediately after GetZoneLocation().
  • SoftReference constructor: SyncZoneLocationFromComponent() runs inside OnLocationResolve(GameObject) once the asset loads, mutating the already-registered ZoneLocation in place. Since ZoneLocation is a reference type, ZoneSystem.m_locations sees the update.

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) <noreply@anthropic.com>
@OrianaVenture
Copy link
Copy Markdown
Contributor

Other custom managers could also benefit from a similar feature! I would argue all fields should be set this way, where if they do not exist it pulls the value from the prefab rather than setting a default value. It will help speed up development so we do not need to specify values in both Unity and in code. Specifying in code can be used for quick testing of changing config values or updates/overrides after the asset bundle has been created.

@MSchmoecker
Copy link
Copy Markdown
Member

MSchmoecker commented Apr 13, 2026

Unfortunately, a change from float to float? breaks backwards compatibility because C# threats them as different types.

E.g. arbitrarily with RtDMonsters, but any other mod that access one of the changed types:

[Info   :   BepInEx] Loading [Jotunn 2.29.0]
[Info   :Jotunn.Main] Initializing ModCompatibility
[Info   :Jotunn.Main] Initializing SynchronizationManager
[Info   :Jotunn.Main] Initializing NetworkManager
[Info   :   BepInEx] Loading [RtDMonsters 2.4.13]
[Info   :Jotunn.Main] Initializing ItemManager
[Info   :Jotunn.Main] Initializing PrefabManager
[Info   :Jotunn.Main] Initializing MockManager
[Info   :Jotunn.Main] Initializing LocalizationManager
[Info   :Jotunn.Main] Initializing AssetManager
[Info   :Jotunn.Main] Initializing CreatureManager
[Error  : Unity Log] MissingMethodException: Method not found: void Jotunn.Configs.LocationConfig.set_ExteriorRadius(single)
Stack trace:
RtDMonsters.RtDMonsters.Awake () (at <fc754e8b679f45f098bf3c74bdd97f8b>:0)
UnityEngine.GameObject:AddComponent(Type)
BepInEx.Bootstrap.Chainloader:Start() (at C:/Users/crypt/RiderProjects/BepInEx/BepInEx/Bootstrap/Chainloader.cs:434)
UnityEngine.GameObject:.cctor()
PlatformInitializer:EarlyInitialize()

It's a MissingMethodException because C# converts getter/setter to methods internally.

@jneb802
Copy link
Copy Markdown
Contributor Author

jneb802 commented Apr 29, 2026

@MSchmoecker I made a change to address backward compatibility and tested with RtDMonsters. I did not get the MissingMethodException error you shared above.

Comment thread JotunnLib/Entities/CustomLocation.cs Outdated
Comment on lines +98 to +102
Location.m_clearArea = locationConfig.ClearArea;
Location.m_exteriorRadius = locationConfig.ExteriorRadius;
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;
Location.m_interiorRadius = locationConfig.InteriorRadius;
if (locationConfig.HasInteriorRadius) Location.m_interiorRadius = locationConfig.InteriorRadius;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this stay as it was before and not check the HasClearArea etc flags?
The Location component is freshly created and thus uses initializes with default values, instead of the fallback values from locationConfig

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MSchmoecker Yes, that's a good point. I made a change to that

Copy link
Copy Markdown
Member

@MSchmoecker MSchmoecker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is good to merge now!

@MSchmoecker MSchmoecker merged commit 5f9c1f8 into Valheim-Modding:dev May 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants