Skip to content

Commit

Permalink
Merge pull request #374 from MSchmoecker/feat/clutter
Browse files Browse the repository at this point in the history
Feat: custom clutter
  • Loading branch information
MSchmoecker authored Sep 4, 2022
2 parents f053e43 + cc0fe13 commit 1ed1f56
Show file tree
Hide file tree
Showing 10 changed files with 450 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog
## Version 2.7.8
* Added custom clutter, check out the tutorial at https://valheim-modding.github.io/Jotunn/tutorials/zones.html#adding-clutter
* Improved mod compatibility window, the disconnect reason is shown inside the Jotunn window
* Fixed mocking of some textures loaded by the game after vanilla prefabs are available

Expand Down
214 changes: 214 additions & 0 deletions JotunnLib/Configs/ClutterConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using System;
using Jotunn.Entities;
using Jotunn.Managers;

namespace Jotunn.Configs
{
/// <summary>
/// Configuration class for adding custom clutter.<br />
/// Use this in a constructor of <see cref="CustomClutter"/>
/// </summary>
public class ClutterConfig
{
/// <summary>
/// Whether this clutter gets spawned in the world. Defaults to <c>true</c>.
/// </summary>
public bool Enabled { get; set; } = true;

/// <summary>
/// Biome to spawn in, multiple Biomes can be allowed with <see cref="ZoneManager.AnyBiomeOf"/>.<br />
/// Default to all biomes.
/// </summary>
public Heightmap.Biome Biome { get; set; } = (Heightmap.Biome)(-1);

/// <summary>
/// Whether this clutter has an <see cref="InstanceRenderer"/> attached that should be used.
/// </summary>
public bool Instanced { get; set; }

/// <summary>
/// The amount of displayed clutter prefabs per patch.
/// For high values an <see cref="InstanceRenderer"/> should be used to lower the amount of overall prefabs.
/// </summary>
public int Amount { get; set; } = 10;

/// <summary>
/// Whether this clutter should be shown on unmodified ground.
/// </summary>
public bool OnUncleared { get; set; } = true;

/// <summary>
/// Whether this clutter should be shown on modified ground.
/// </summary>
public bool OnCleared { get; set; }

/// <summary>
/// Minimum random size of the prefab, only active when <see cref="Instanced"/> is used.
/// </summary>
public float ScaleMin { get; set; } = 1f;

/// <summary>
/// Maximum random size of the prefab, only active when <see cref="Instanced"/> is used.
/// </summary>
public float ScaleMax { get; set; } = 1f;

/// <summary>
/// Maximum terrain tilt in degrees this clutter will be placed on.
/// </summary>
public float MaxTilt { get; set; } = 25f;

/// <summary>
/// Minimum terrain height this clutter will be placed on.
/// </summary>
public float MinAltitude { get; set; }

/// <summary>
/// Maximum terrain height this clutter will be placed on.
/// </summary>
public float MaxAltitude { get; set; } = 1000f;

/// <summary>
/// Whether the y position will always be at water level.
/// Used before <see cref="RandomOffset"/>
/// </summary>
public bool SnapToWater { get; set; }

/// <summary>
/// Random y offset of every individual prefab.
/// Calculated after <see cref="SnapToWater"/>.
/// </summary>
public float RandomOffset { get; set; }

/// <summary>
/// Whether this clutter will be rotated with the underlying terrain.
/// Otherwise it will always point straight up.
/// </summary>
public bool TerrainTilt { get; set; }

/// <summary>
/// Whether the clutter should check for ocean height.
/// </summary>
public bool OceanDepthCheck { get; set; }

/// <summary>
/// Minimum ocean depth that is needed to place this clutter.
/// Needs <see cref="OceanDepthCheck"/> to be <c>true</c>.
/// </summary>
public float MinOceanDepth { get; set; }

/// <summary>
/// Maximum ocean depth to place this clutter.
/// Needs <see cref="OceanDepthCheck"/> to be <c>true</c>.
/// </summary>
public float MaxOceanDepth { get; set; }

/// <summary>
/// Whether the clutter should check for forest thresholds.
/// </summary>
public bool InForest { get; set; }

/// <summary>
/// Minimum value of the forest fractal:<br/>
/// 0 - 1: inside the forest<br/>
/// 1: forest edge<br/>
/// 1 - infinity: outside the forest
/// </summary>
public float ForestThresholdMin { get; set; }

/// <summary>
/// Maximum value of the forest fractal:<br/>
/// 0 - 1: inside the forest<br/>
/// 1: forest edge<br/>
/// 1 - infinity: outside the forest
/// </summary>
public float ForestThresholdMax { get; set; } = 1f;

/// <summary>
/// Size of a noise map used to determine if the clutter should be placed.
/// Set to 0 to disable and place it everywhere.
/// </summary>
public float FractalScale { get; set; }

/// <summary>
/// Offset of the noise map.
/// </summary>
public float FractalOffset { get; set; }

/// <summary>
/// Minimum value of the noise map that is needed to place the clutter.
/// </summary>
public float FractalThresholdMin { get; set; } = 0.5f;

/// <summary>
/// Maximum value of the noise map to place the clutter.
/// </summary>
public float FractalThresholdMax { get; set; } = 1f;

/// <summary>
/// Create a new <see cref="ClutterConfig"/>
/// </summary>
public ClutterConfig() {}

/// <summary>
/// Create a copy of the <see cref="ClutterSystem.Clutter"/>
/// </summary>
/// <param name="clutter"></param>
public ClutterConfig(ClutterSystem.Clutter clutter)
{
Enabled = clutter.m_enabled;
Biome = clutter.m_biome;
Instanced = clutter.m_instanced;
Amount = clutter.m_amount;
OnUncleared = clutter.m_onUncleared;
OnCleared = clutter.m_onCleared;
ScaleMin = clutter.m_scaleMin;
ScaleMax = clutter.m_scaleMax;
MaxTilt = clutter.m_maxTilt;
MinAltitude = clutter.m_minAlt;
MaxAltitude = clutter.m_maxAlt;
SnapToWater = clutter.m_snapToWater;
RandomOffset = clutter.m_randomOffset;
TerrainTilt = clutter.m_terrainTilt;
OceanDepthCheck = Math.Abs(clutter.m_minOceanDepth - clutter.m_maxOceanDepth) > 0.001f;
MinOceanDepth = clutter.m_minOceanDepth;
MaxOceanDepth = clutter.m_maxOceanDepth;
InForest = clutter.m_inForest;
ForestThresholdMin = clutter.m_forestTresholdMin;
ForestThresholdMax = clutter.m_forestTresholdMax;
FractalScale = clutter.m_fractalScale;
FractalOffset = clutter.m_fractalOffset;
FractalThresholdMin = clutter.m_fractalTresholdMin;
FractalThresholdMax = clutter.m_fractalTresholdMax;
}

internal ClutterSystem.Clutter ToClutter()
{
return new ClutterSystem.Clutter()
{
m_enabled = Enabled,
m_biome = Biome,
m_instanced = Instanced,
m_amount = Amount,
m_onUncleared = OnUncleared,
m_onCleared = OnCleared,
m_scaleMin = ScaleMin,
m_scaleMax = ScaleMax,
m_maxTilt = MaxTilt,
m_minAlt = MinAltitude,
m_maxAlt = MaxAltitude,
m_snapToWater = SnapToWater,
m_randomOffset = RandomOffset,
m_terrainTilt = TerrainTilt,
m_minOceanDepth = OceanDepthCheck ? MinOceanDepth : 0,
m_maxOceanDepth = OceanDepthCheck ? MaxOceanDepth : 0,
m_inForest = InForest,
m_forestTresholdMin = ForestThresholdMin,
m_forestTresholdMax = ForestThresholdMax,
m_fractalScale = FractalScale,
m_fractalOffset = FractalOffset,
m_fractalTresholdMin = FractalThresholdMin,
m_fractalTresholdMax = FractalThresholdMax,
};
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion JotunnLib/Documentation/tutorials/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Each section will have examples showing how this is done. All of the examples sh
* [Recipes](recipes.md): Add requirements for items and tie them to crafting stations.
* [Status Effects](status-effects.md): Add custom Status Effects.
* [Skills](skills.md): Add custom trainable Skills.
* [Zones / Locations / Vegetation](zones.md): Add to or change Valheim's world generation using your custom assets.
* [Zones / Locations / Vegetation / Clutter](zones.md): Add to or change Valheim's world generation using your custom assets.

### Utilities

Expand Down
55 changes: 50 additions & 5 deletions JotunnLib/Documentation/tutorials/zones.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Zones / Locations / Vegetation
# Zones / Locations / Vegetation / Clutter

The world of Valheim is split up into *Zones*, each 64 by 64 meters, controlled by the *ZoneSystem*.
Each *Zone* can potentially have a single *Location* instance, and many *Vegetation* instances.
Expand Down Expand Up @@ -30,6 +30,12 @@ This is most of the spawned objects:

There is no limit to the number of vegetation in a single zone.

## Clutter
Clutter are client side prefabs that are scattered around on the ground.
This means they aren't saved to the world nor synced in multiplayer.
However, they can still have animations, shaders or behaviour that reacts to the world.


## World generation

Valheim's worlds are procedurally generated exactly once using a provided seed.
Expand Down Expand Up @@ -97,20 +103,46 @@ Every attempt to place an instance of a Vegetation will go through these checks:

Locations have a property `ClearArea`, enabling this will prevent _any_ vegetation from spawning in the `ExteriorRadius` of the location.

## Clutter
Clutter is newly generated in a radius around the player and destroyed if the player gets too far away.

Every clutter only checks it's own properties to determine if it should get placed, they cannot block each other.

First, those conditions are checked:

| Property | Effect |
|------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| InForest<br>ForestThresholdMin<br>ForestThresholdMax | If enabled, the value of the forest fractal in the seed of this clutter must be between these values<br>0 is the center of the forest.<br>1 is on the edge of the forest<br>Above 1 will move the clutter further from the forest edge. |
| FractalScale<br>FractalThresholdMin<br>FractalThresholdMax | If FractalScale is greater then one, the value (between 0 and 1) of a scaled fractal noise map of this clutter must be between the min and max values in order to be placed. |
| Biome | The clutter must be in a biome that matches this field. You can specify multiple biomes, any of these will be matched. |
| MinAltitude<br>MaxAltitude | The height where the clutter should be placed these values. 0 is the shoreline. |
| MaxTilt | Checks the slope of the terrain mesh at this point, using the normal of the mesh<br>In degrees: 0 is flat ground, 90 is a vertical wall. |
| OceanDepthCheck<br/>MinOceanDepth<br>MaxOceanDepth | If OceanDepthCheck is enabled, check that the value of the OceanDepth from the seed is between these values.<br>This is not the same as Altitude, not entirely sure how to interpret this. |
| OnCleared<br/>OnUncleared | Checks if the clutter should be placed on modified and/or unmodified ground. |

After all checks, the placement values will be calculated:

| Property | Effect |
|-----------------------|--------------------------------------------------------------------------------------------------------------------|
| SnapToWater | If enabled, spawns the clutter at the water level height instead of ground height. |
| RandomOffset | If greater then one, the y position will be random around this value. |
| TerrainTilt | If enabled, the underlying terrain tilt will be used for the rotation. Otherwise it will always point straight up. |
| ScaleMin<br/>ScaleMax | If an instance renderer is used, the clutter size will be random between those values |

# ZoneManager

Jötunn provides a [ZoneManager](xref:Jotunn.Managers.ZoneManager) singleton for mods to interact with the ZoneSystem of Valheim.

## Timing

Locations and Vegetation are loaded during world load.
You can add your custom locations and vegetation to the manager as early as your mods Awake().
Locations, Vegetation and clutter are loaded during world load.
You can add your custom locations, vegetation and clutter to the manager as early as your mods Awake().

You can use the event [ZoneManager.OnVanillaLocationsAvailable](xref:Jotunn.Managers.ZoneManager.OnVanillaLocationsAvailable) to get a callback to clone or modify vanilla locations when those are available for use but before your custom entites get injected to the game.
This is called every time a world loads.

Adding custom and cloned locations & vegetation must only be done once.
Modifications to vanilla locations & vegetation must be repeated every time!
Adding custom and cloned locations, vegetation and clutter must only be done once.
Modifications to vanilla locations, vegetation and clutter must be repeated every time!

## Locations

Expand Down Expand Up @@ -228,3 +260,16 @@ CustomVegetation customVegetation = new CustomVegetation(lulzCubePrefab, false,
This example defines very little filters, so this prefab will be found all over every Meadows.

![Lulzcube vegetation](../images/data/customVegetation.png)


### Adding Clutter
The prefab can be loaded directly from an AssetBundle:
```cs
AssetBundle ClutterAssetBundle = AssetUtils.LoadAssetBundleFromResources("clutterbundle");

ClutterConfig stone = new ClutterConfig();
stone.Amount = 5;
ZoneManager.Instance.AddCustomClutter(new CustomClutter(ClutterAssetBundle, "TestStone", false, stone));
```

![test stone clutter](../images/data/customClutter.png)
77 changes: 77 additions & 0 deletions JotunnLib/Entities/CustomClutter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Jotunn.Configs;
using Jotunn.Managers;
using UnityEngine;

namespace Jotunn.Entities
{
/// <summary>
/// Main interface for adding custom clutter to the game.<br />
/// Clutter are client side only objects scattered on the ground.<br />
/// All custom clutter have to be wrapped inside this class to add it to Jötunns <see cref="ZoneManager"/>.
/// </summary>
public class CustomClutter : CustomEntity
{
/// <summary>
/// The prefab for this custom clutter.
/// </summary>
public GameObject Prefab { get; }

/// <summary>
/// Associated <see cref="ClutterSystem.Clutter"/> class.
/// </summary>
public ClutterSystem.Clutter Clutter { get; }

/// <summary>
/// Name of this custom clutter.
/// </summary>
public string Name { get; }

/// <summary>
/// Indicator if references from <see cref="Entities.Mock{T}"/>s will be replaced at runtime.
/// </summary>
public bool FixReference { get; set; }

/// <summary>
/// Custom clutter from a prefab.<br />
/// Can fix references for mocks.
/// </summary>
/// <param name="prefab">The prefab for this custom clutter.</param>
/// <param name="fixReference">If true references for <see cref="Entities.Mock{T}"/> objects get resolved at runtime by Jötunn.</param>
/// <param name="config">The <see cref="ClutterConfig"/> for this custom vegation.</param>
public CustomClutter(GameObject prefab, bool fixReference, ClutterConfig config)
{
Prefab = prefab;
Name = prefab.name;
Clutter = config.ToClutter();
Clutter.m_prefab = prefab;
FixReference = fixReference;
}

/// <summary>
/// Custom clutter from a prefab loaded from an <see cref="AssetBundle"/>.<br />
/// Can fix references for mocks.
/// </summary>
/// <param name="assetBundle">A preloaded <see cref="AssetBundle"/></param>
/// <param name="assetName">Name of the prefab in the bundle.</param>
/// <param name="fixReference">If true references for <see cref="Entities.Mock{T}"/> objects get resolved at runtime by Jötunn.</param>
/// <param name="config">The <see cref="ClutterConfig"/> for this custom clutter.</param>
public CustomClutter(AssetBundle assetBundle, string assetName, bool fixReference, ClutterConfig config)
{
var prefab = assetBundle.LoadAsset<GameObject>(assetName);
if (prefab)
{
Prefab = prefab;
Name = prefab.name;
Clutter = config.ToClutter();
Clutter.m_prefab = prefab;
FixReference = fixReference;
}
}

/// <inheritdoc/>
public override string ToString()
{
return Name;
}
}
}
Loading

0 comments on commit 1ed1f56

Please sign in to comment.