Procgen biomes (#13487)
* Planetmap tiles Biomes etc etc * a * oop * Chunk-based rendering * funny * Less allocations * Fix overdraw * Content tile edge support Also updated grass to use it as a POC. * Kindly revert * Update for variant edges * fixes * Use fastnoise * Remove redundant group * a * refactor a fair bit * Prototype data instead * tweaks * a * fix maths * working * a * Slightly better empty support * a * flowers * sounds * lewd * Networking * more fixes * better * colours * Some chunk loading * Proper loading and unloading * Better loading * Fix parallax and movement sounds * Anchoring support + decal setup * Most of the way to load and unload * Decal loading kinda werkin * large trees * started diffing * a * Variant support and deserts * a * snow * agony, even * working again * todo * a * laba tiles * aeiou * a # Conflicts: # Resources/Prototypes/Entities/Tiles/planet.yml # Resources/Prototypes/Tiles/planet.yml # Resources/Textures/Tiles/Planet/Lava/lava.rsi/meta.json * laba * Add lava * Initial ignition * triggers * a * a * y * Add basalt tiles Did some unconventional things for the animation + rocks. * fixies * mergies * promotion * lava biome * Lava planet start * cleanup and more lava * laba * maccas * biome stuf * weh * bongflicts * aeaeae * More fixes * a * these too
@@ -1,4 +1,6 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Client.IconSmoothing
|
||||
{
|
||||
|
||||
8
Content.Client/Parallax/BiomeSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
|
||||
namespace Content.Client.Parallax;
|
||||
|
||||
public sealed class BiomeSystem : SharedBiomeSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -11,9 +12,11 @@ namespace Content.Client.Parallax;
|
||||
|
||||
public sealed class ParallaxOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IParallaxManager _manager = default!;
|
||||
private readonly ParallaxSystem _parallax;
|
||||
|
||||
@@ -23,8 +26,15 @@ public sealed class ParallaxOverlay : Overlay
|
||||
{
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_parallax = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ParallaxSystem>();
|
||||
_parallax = _entManager.System<ParallaxSystem>();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapManager.GetMapEntityId(args.MapId)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Content.MapRenderer.Painters
|
||||
{
|
||||
public sealed class TilePainter
|
||||
{
|
||||
private const string TilesPath = "/Textures/Tiles/";
|
||||
public const int TileImageSize = EyeManager.PixelsPerMeter;
|
||||
|
||||
private readonly ITileDefinitionManager _sTileDefinitionManager;
|
||||
|
||||
@@ -6,11 +6,13 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Parallax;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Maps;
|
||||
|
||||
@@ -22,13 +24,15 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public string Command => $"planet";
|
||||
public string Description => Loc.GetString("cmd-planet-desc");
|
||||
public string Help => Loc.GetString("cmd-planet-help", ("command", Command));
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString($"cmd-planet-args"));
|
||||
return;
|
||||
@@ -48,16 +52,35 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_protoManager.HasIndex<BiomePrototype>(args[1]))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-planet-map-prototype", ("prototype", args[1])));
|
||||
return;
|
||||
}
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
MetaDataComponent? metadata = null;
|
||||
|
||||
var parallax = _entManager.EnsureComponent<ParallaxComponent>(mapUid);
|
||||
parallax.Parallax = "Grass";
|
||||
_entManager.Dirty(parallax, metadata);
|
||||
var biome = _entManager.EnsureComponent<BiomeComponent>(mapUid);
|
||||
biome.BiomePrototype = args[1];
|
||||
biome.Seed = _random.Next();
|
||||
_entManager.Dirty(biome);
|
||||
|
||||
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
|
||||
gravity.Enabled = true;
|
||||
_entManager.Dirty(gravity);
|
||||
_entManager.EnsureComponent<MapLightComponent>(mapUid);
|
||||
_entManager.Dirty(gravity, metadata);
|
||||
|
||||
// Day lighting
|
||||
// Daylight: #D8B059
|
||||
// Midday: #E6CB8B
|
||||
// Moonlight: #2b3143
|
||||
// Lava: #A34931
|
||||
|
||||
var light = _entManager.EnsureComponent<MapLightComponent>(mapUid);
|
||||
light.AmbientLightColor = Color.FromHex("#D8B059");
|
||||
_entManager.Dirty(light, metadata);
|
||||
|
||||
// Atmos
|
||||
var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid);
|
||||
|
||||
atmos.Space = false;
|
||||
@@ -71,24 +94,27 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
Moles = moles,
|
||||
};
|
||||
|
||||
var footstep = _entManager.EnsureComponent<FootstepModifierComponent>(mapUid);
|
||||
footstep.Sound = new SoundCollectionSpecifier("FootstepGrass");
|
||||
_entManager.Dirty(footstep);
|
||||
|
||||
_entManager.EnsureComponent<MapGridComponent>(mapUid);
|
||||
shell.WriteLine(Loc.GetString("cmd-planet-success", ("mapId", mapId)));
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.Empty;
|
||||
var options = _entManager.EntityQuery<MapComponent>(true)
|
||||
.Select(o => new CompletionOption(o.WorldMap.ToString(), "MapId"));
|
||||
|
||||
return CompletionResult.FromOptions(options);
|
||||
}
|
||||
|
||||
var options = _entManager.EntityQuery<MapComponent>(true)
|
||||
.Select(o => new CompletionOption(o.WorldMap.ToString(), "MapId"));
|
||||
if (args.Length == 2)
|
||||
{
|
||||
var options = _protoManager.EnumeratePrototypes<BiomePrototype>()
|
||||
.Select(o => new CompletionOption(o.ID, "Biome"));
|
||||
return CompletionResult.FromOptions(options);
|
||||
}
|
||||
|
||||
return CompletionResult.FromOptions(options);
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
301
Content.Server/Parallax/BiomeSystem.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
using Content.Server.Decals;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Content.Server.Parallax;
|
||||
|
||||
public sealed class BiomeSystem : SharedBiomeSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly DecalSystem _decals = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _handledEntities = new();
|
||||
private const float LoadRange = ChunkSize * 2f;
|
||||
private readonly Box2 _loadArea = new(-LoadRange, -LoadRange, LoadRange, LoadRange);
|
||||
|
||||
private readonly Dictionary<BiomeComponent, HashSet<Vector2i>> _activeChunks = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BiomeComponent, MapInitEvent>(OnBiomeMapInit);
|
||||
}
|
||||
|
||||
private void OnBiomeMapInit(EntityUid uid, BiomeComponent component, MapInitEvent args)
|
||||
{
|
||||
component.Seed = _random.Next();
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var biomeQuery = GetEntityQuery<BiomeComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var biomes = AllEntityQuery<BiomeComponent>();
|
||||
|
||||
while (biomes.MoveNext(out var biome))
|
||||
{
|
||||
_activeChunks.Add(biome, new HashSet<Vector2i>());
|
||||
}
|
||||
|
||||
// Get chunks in range
|
||||
foreach (var client in Filter.GetAllPlayers(_playerManager))
|
||||
{
|
||||
var pSession = (IPlayerSession) client;
|
||||
|
||||
if (xformQuery.TryGetComponent(pSession.AttachedEntity, out var xform) &&
|
||||
_handledEntities.Add(pSession.AttachedEntity.Value) &&
|
||||
biomeQuery.TryGetComponent(xform.MapUid, out var biome))
|
||||
{
|
||||
AddChunksInRange(biome, _transform.GetWorldPosition(xform, xformQuery));
|
||||
}
|
||||
|
||||
foreach (var viewer in pSession.ViewSubscriptions)
|
||||
{
|
||||
if (!_handledEntities.Add(viewer) ||
|
||||
!xformQuery.TryGetComponent(viewer, out xform) ||
|
||||
!biomeQuery.TryGetComponent(xform.MapUid, out biome))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddChunksInRange(biome, _transform.GetWorldPosition(xform, xformQuery));
|
||||
}
|
||||
}
|
||||
|
||||
var loadBiomes = AllEntityQuery<BiomeComponent, MapGridComponent>();
|
||||
|
||||
while (loadBiomes.MoveNext(out var biome, out var grid))
|
||||
{
|
||||
var noise = new FastNoise(biome.Seed);
|
||||
var gridUid = grid.Owner;
|
||||
|
||||
// Load new chunks
|
||||
LoadChunks(biome, gridUid, grid, noise);
|
||||
// Unload old chunks
|
||||
UnloadChunks(biome, gridUid, grid, noise);
|
||||
}
|
||||
|
||||
_handledEntities.Clear();
|
||||
_activeChunks.Clear();
|
||||
}
|
||||
|
||||
private void AddChunksInRange(BiomeComponent biome, Vector2 worldPos)
|
||||
{
|
||||
var enumerator = new ChunkIndicesEnumerator(_loadArea.Translated(worldPos), ChunkSize);
|
||||
|
||||
while (enumerator.MoveNext(out var chunkOrigin))
|
||||
{
|
||||
_activeChunks[biome].Add(chunkOrigin.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, FastNoise noise)
|
||||
{
|
||||
var active = _activeChunks[component];
|
||||
var prototype = ProtoManager.Index<BiomePrototype>(component.BiomePrototype);
|
||||
List<(Vector2i, Tile)>? tiles = null;
|
||||
|
||||
foreach (var chunk in active)
|
||||
{
|
||||
if (!component.LoadedChunks.Add(chunk))
|
||||
continue;
|
||||
|
||||
tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
|
||||
// Load NOW!
|
||||
LoadChunk(component, gridUid, grid, chunk * ChunkSize, noise, prototype, tiles);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, FastNoise noise, BiomePrototype prototype, List<(Vector2i, Tile)> tiles)
|
||||
{
|
||||
component.ModifiedTiles.TryGetValue(chunk, out var modified);
|
||||
modified ??= new HashSet<Vector2i>();
|
||||
|
||||
// Set tiles first
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var indices = new Vector2i(x + chunk.X, y + chunk.Y);
|
||||
|
||||
if (modified.Contains(indices))
|
||||
continue;
|
||||
|
||||
// If there's existing data then don't overwrite it.
|
||||
if (grid.TryGetTileRef(indices, out var tileRef) && !tileRef.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
// Pass in null so we don't try to get the tileref.
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, null, out var biomeTile) || biomeTile.Value == tileRef.Tile)
|
||||
continue;
|
||||
|
||||
tiles.Add((indices, biomeTile.Value));
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
tiles.Clear();
|
||||
|
||||
// Now do entities
|
||||
var loadedEntities = new List<EntityUid>();
|
||||
component.LoadedEntities.Add(chunk, loadedEntities);
|
||||
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var indices = new Vector2i(x + chunk.X, y + chunk.Y);
|
||||
|
||||
if (modified.Contains(indices))
|
||||
continue;
|
||||
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetEntity(indices, prototype, noise, grid, out var entPrototype))
|
||||
continue;
|
||||
|
||||
// TODO: Fix non-anchored ents spawning.
|
||||
// Just track loaded chunks for now.
|
||||
var ent = Spawn(entPrototype, grid.GridTileToLocal(indices));
|
||||
loadedEntities.Add(ent);
|
||||
}
|
||||
}
|
||||
|
||||
// Decals
|
||||
var loadedDecals = new Dictionary<uint, Vector2i>();
|
||||
component.LoadedDecals.Add(chunk, loadedDecals);
|
||||
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var indices = new Vector2i(x + chunk.X, y + chunk.Y);
|
||||
|
||||
if (modified.Contains(indices))
|
||||
continue;
|
||||
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetDecals(indices, prototype, noise, grid, out var decals))
|
||||
continue;
|
||||
|
||||
foreach (var decal in decals)
|
||||
{
|
||||
if (!_decals.TryAddDecal(decal.ID, new EntityCoordinates(gridUid, decal.Position), out var dec))
|
||||
continue;
|
||||
|
||||
loadedDecals.Add(dec, indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modified.Count == 0)
|
||||
{
|
||||
component.ModifiedTiles.Remove(chunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.ModifiedTiles[chunk] = modified;
|
||||
}
|
||||
}
|
||||
|
||||
private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, FastNoise noise)
|
||||
{
|
||||
var active = _activeChunks[component];
|
||||
List<(Vector2i, Tile)>? tiles = null;
|
||||
|
||||
foreach (var chunk in component.LoadedChunks)
|
||||
{
|
||||
if (active.Contains(chunk) || !component.LoadedChunks.Remove(chunk))
|
||||
continue;
|
||||
|
||||
// Unload NOW!
|
||||
tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
|
||||
UnloadChunk(component, gridUid, grid, chunk * ChunkSize, noise, tiles);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, FastNoise noise, List<(Vector2i, Tile)> tiles)
|
||||
{
|
||||
// Reverse order to loading
|
||||
var prototype = ProtoManager.Index<BiomePrototype>(component.BiomePrototype);
|
||||
component.ModifiedTiles.TryGetValue(chunk, out var modified);
|
||||
modified ??= new HashSet<Vector2i>();
|
||||
|
||||
// Delete decals
|
||||
foreach (var (dec, indices) in component.LoadedDecals[chunk])
|
||||
{
|
||||
// If we couldn't remove it then flag the tile to never be touched.
|
||||
if (!_decals.RemoveDecal(gridUid, dec))
|
||||
{
|
||||
modified.Add(indices);
|
||||
}
|
||||
}
|
||||
|
||||
component.LoadedDecals.Remove(chunk);
|
||||
|
||||
// Delete entities
|
||||
// This is a TODO
|
||||
// Ideally any entities that aren't modified just get deleted and re-generated later
|
||||
// This is because if we want to save the map (e.g. persistent server) it makes the file much smaller
|
||||
// and also if the map is enormous will make stuff like physics broadphase much faster
|
||||
// For now we'll just leave them because no entity diffs.
|
||||
|
||||
component.LoadedEntities.Remove(chunk);
|
||||
|
||||
// Unset tiles (if the data is custom)
|
||||
|
||||
for (var x = 0; x < ChunkSize; x++)
|
||||
{
|
||||
for (var y = 0; y < ChunkSize; y++)
|
||||
{
|
||||
var indices = new Vector2i(x + chunk.X, y + chunk.Y);
|
||||
|
||||
if (modified.Contains(indices))
|
||||
continue;
|
||||
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
|
||||
if (anchored.MoveNext(out _))
|
||||
{
|
||||
modified.Add(indices);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's default data unload the tile.
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, null, out var biomeTile) ||
|
||||
grid.TryGetTileRef(indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
|
||||
{
|
||||
modified.Add(indices);
|
||||
continue;
|
||||
}
|
||||
|
||||
tiles.Add((indices, Tile.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
tiles.Clear();
|
||||
component.LoadedChunks.Remove(chunk);
|
||||
|
||||
if (modified.Count == 0)
|
||||
{
|
||||
component.ModifiedTiles.Remove(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@ namespace Content.Shared.Maps
|
||||
public string Name { get; private set; } = "";
|
||||
[DataField("sprite")] public ResourcePath? Sprite { get; }
|
||||
|
||||
[DataField("cornerSprites")] public List<ResourcePath> CornerSprites { get; } = new();
|
||||
|
||||
[DataField("cardinalSprites")] public List<ResourcePath> CardinalSprites { get; } = new();
|
||||
|
||||
[DataField("isSubfloor")] public bool IsSubFloor { get; private set; }
|
||||
|
||||
[DataField("baseTurfs")] public List<string> BaseTurfs { get; } = new();
|
||||
|
||||
@@ -17,6 +17,10 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Mech.Components;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
@@ -32,6 +36,7 @@ namespace Content.Shared.Movement.Systems
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly SharedBiomeSystem _biome = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
@@ -449,7 +454,7 @@ namespace Content.Shared.Movement.Systems
|
||||
}
|
||||
|
||||
if (_inventory.TryGetSlotEntity(mover.Owner, "shoes", out var shoes) &&
|
||||
EntityManager.TryGetComponent<FootstepModifierComponent>(shoes, out var modifier))
|
||||
TryComp<FootstepModifierComponent>(shoes, out var modifier))
|
||||
{
|
||||
sound = modifier.Sound;
|
||||
return true;
|
||||
@@ -461,10 +466,10 @@ namespace Content.Shared.Movement.Systems
|
||||
private bool TryGetFootstepSound(TransformComponent xform, bool haveShoes, [NotNullWhen(true)] out SoundSpecifier? sound)
|
||||
{
|
||||
sound = null;
|
||||
MapGridComponent? grid;
|
||||
|
||||
// Fallback to the map
|
||||
if (xform.MapUid == xform.GridUid ||
|
||||
xform.GridUid == null)
|
||||
// Fallback to the map?
|
||||
if (xform.GridUid == null)
|
||||
{
|
||||
if (TryComp<FootstepModifierComponent>(xform.MapUid, out var modifier))
|
||||
{
|
||||
@@ -475,25 +480,30 @@ namespace Content.Shared.Movement.Systems
|
||||
return false;
|
||||
}
|
||||
|
||||
var grid = _mapManager.GetGrid(xform.GridUid.Value);
|
||||
var tile = grid.GetTileRef(xform.Coordinates);
|
||||
|
||||
if (tile.IsSpace(_tileDefinitionManager))
|
||||
return false;
|
||||
grid = _mapManager.GetGrid(xform.GridUid.Value);
|
||||
var position = grid.LocalToTile(xform.Coordinates);
|
||||
|
||||
// If the coordinates have a FootstepModifier component
|
||||
// i.e. component that emit sound on footsteps emit that sound
|
||||
foreach (var maybeFootstep in grid.GetAnchoredEntities(tile.GridIndices))
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(position);
|
||||
|
||||
while (anchored.MoveNext(out var maybeFootstep))
|
||||
{
|
||||
if (EntityManager.TryGetComponent(maybeFootstep, out FootstepModifierComponent? footstep))
|
||||
if (TryComp<FootstepModifierComponent>(maybeFootstep, out var footstep))
|
||||
{
|
||||
sound = footstep.Sound;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!grid.TryGetTileRef(position, out var tileRef))
|
||||
{
|
||||
sound = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Walking on a tile.
|
||||
var def = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
|
||||
var def = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId];
|
||||
sound = haveShoes ? def.FootstepSounds : def.BarestepSounds;
|
||||
return sound != null;
|
||||
}
|
||||
|
||||
39
Content.Shared/Parallax/Biomes/BiomeComponent.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed class BiomeComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("seed")]
|
||||
public int Seed;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite),
|
||||
DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer<BiomePrototype>))]
|
||||
public string BiomePrototype = "Grasslands";
|
||||
|
||||
// TODO: Need to flag tiles as not requiring custom data anymore, e.g. if we spawn an ent and don't unspawn it.
|
||||
|
||||
/// <summary>
|
||||
/// If we've already generated a tile and couldn't deload it then we won't ever reload it in future.
|
||||
/// Stored by [Chunkorigin, Tiles]
|
||||
/// </summary>
|
||||
[DataField("modifiedTiles")]
|
||||
public Dictionary<Vector2i, HashSet<Vector2i>> ModifiedTiles = new();
|
||||
|
||||
/// <summary>
|
||||
/// Decals that have been loaded as a part of this biome.
|
||||
/// </summary>
|
||||
[DataField("decals")]
|
||||
public Dictionary<Vector2i, Dictionary<uint, Vector2i>> LoadedDecals = new();
|
||||
|
||||
[DataField("entities")]
|
||||
public Dictionary<Vector2i, List<EntityUid>> LoadedEntities = new();
|
||||
|
||||
/// <summary>
|
||||
/// Currently active chunks
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly HashSet<Vector2i> LoadedChunks = new();
|
||||
}
|
||||
123
Content.Shared/Parallax/Biomes/BiomePrototype.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
[Prototype("biome")]
|
||||
public sealed class BiomePrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("layers")]
|
||||
public List<IBiomeLayer> Layers = new();
|
||||
}
|
||||
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public interface IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Threshold for this layer to be present. If set to 0 forces it for every tile.
|
||||
/// </summary>
|
||||
float Threshold { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset the seed by the specified amount for this layer.
|
||||
/// Useful if you have 2 similar layers but don't want them to match exactly.
|
||||
/// </summary>
|
||||
int SeedOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Frequency for noise: lower values create larger blobs.
|
||||
/// </summary>
|
||||
float Frequency { get; }
|
||||
}
|
||||
|
||||
public sealed class BiomeTileLayer : IBiomeLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("seedOffset")]
|
||||
public int SeedOffset { get; } = 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("frequency")]
|
||||
public float Frequency { get; } = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// Which tile variants to use for this layer. Uses all of the tile's variants if none specified
|
||||
/// </summary>
|
||||
[DataField("variants")]
|
||||
public List<byte>? Variants = null;
|
||||
|
||||
[DataField("tile", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>))]
|
||||
public string Tile = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles actual objects such as decals and entities.
|
||||
/// </summary>
|
||||
public interface IBiomeWorldLayer : IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// What tiles we're allowed to spawn on, real or biome.
|
||||
/// </summary>
|
||||
List<string> AllowedTiles { get; }
|
||||
}
|
||||
|
||||
public sealed class BiomeDecalLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Divide each tile up by this amount.
|
||||
/// </summary>
|
||||
[DataField("divisions")]
|
||||
public float Divisions = 1f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("seedOffset")]
|
||||
public int SeedOffset { get; } = 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("frequency")]
|
||||
public float Frequency { get; } = 0.25f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.8f;
|
||||
|
||||
[DataField("decals", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<DecalPrototype>))]
|
||||
public List<string> Decals = new();
|
||||
}
|
||||
|
||||
public sealed class BiomeEntityLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; } = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("seedOffset")]
|
||||
public int SeedOffset { get; } = 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("frequency")]
|
||||
public float Frequency { get; } = 0.1f;
|
||||
|
||||
[DataField("entities", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> Entities = new();
|
||||
}
|
||||
333
Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
public abstract class SharedBiomeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager ProtoManager = default!;
|
||||
[Dependency] protected readonly ITileDefinitionManager TileDefManager = default!;
|
||||
|
||||
protected const byte ChunkSize = 4;
|
||||
|
||||
// TODO: After I wrote all of this FastNoiseLite got ported so this needs updating for that don't @ me
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BiomeComponent, ComponentGetState>(OnBiomeGetState);
|
||||
SubscribeLocalEvent<BiomeComponent, ComponentHandleState>(OnBiomeHandleState);
|
||||
}
|
||||
|
||||
private void OnBiomeHandleState(EntityUid uid, BiomeComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not BiomeComponentState state)
|
||||
return;
|
||||
|
||||
component.Seed = state.Seed;
|
||||
}
|
||||
|
||||
private void OnBiomeGetState(EntityUid uid, BiomeComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new BiomeComponentState(component.Seed, component.BiomePrototype);
|
||||
}
|
||||
|
||||
protected T Pick<T>(List<T> collection, float value)
|
||||
{
|
||||
DebugTools.Assert(value is >= 0f and <= 1f);
|
||||
|
||||
if (collection.Count == 1)
|
||||
return collection[0];
|
||||
|
||||
value *= collection.Count;
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
value -= 1f;
|
||||
|
||||
if (value <= 0f)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
protected int Pick(int count, float value)
|
||||
{
|
||||
DebugTools.Assert(value is >= 0f and <= 1f);
|
||||
|
||||
if (count == 1)
|
||||
return 0;
|
||||
|
||||
value *= count;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
value -= 1f;
|
||||
|
||||
if (value <= 0f)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (grid.TryGetTileRef(indices, out var tileRef))
|
||||
{
|
||||
tile = tileRef.Tile;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryComp<BiomeComponent>(uid, out var biome))
|
||||
{
|
||||
tile = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetBiomeTile(indices, ProtoManager.Index<BiomePrototype>(biome.BiomePrototype),
|
||||
new FastNoise(biome.Seed), grid, out tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the tile, real or otherwise, for the specified indices.
|
||||
/// </summary>
|
||||
public bool TryGetBiomeTile(Vector2i indices, BiomePrototype prototype, FastNoise seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (grid?.TryGetTileRef(indices, out var tileRef) == true && !tileRef.Tile.IsEmpty)
|
||||
{
|
||||
tile = tileRef.Tile;
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldFrequency = seed.GetFrequency();
|
||||
|
||||
for (var i = prototype.Layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = prototype.Layers[i];
|
||||
|
||||
if (layer is not BiomeTileLayer tileLayer)
|
||||
continue;
|
||||
|
||||
seed.SetFrequency(tileLayer.Frequency);
|
||||
|
||||
if (TryGetTile(indices, seed, tileLayer.Threshold, ProtoManager.Index<ContentTileDefinition>(tileLayer.Tile), tileLayer.Variants, out tile))
|
||||
{
|
||||
seed.SetFrequency(oldFrequency);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
seed.SetFrequency(oldFrequency);
|
||||
tile = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the relevant entity for this tile.
|
||||
/// </summary>
|
||||
protected bool TryGetEntity(Vector2i indices, BiomePrototype prototype, FastNoise noise, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, grid, out var tileRef))
|
||||
{
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tileId = TileDefManager[tileRef.Value.TypeId].ID;
|
||||
var oldFrequency = noise.GetFrequency();
|
||||
var seed = noise.GetSeed();
|
||||
|
||||
for (var i = prototype.Layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = prototype.Layers[i];
|
||||
var offset = 0;
|
||||
|
||||
// Decals might block entity so need to check if there's one in front of us.
|
||||
switch (layer)
|
||||
{
|
||||
case IBiomeWorldLayer worldLayer:
|
||||
if (!worldLayer.AllowedTiles.Contains(tileId))
|
||||
continue;
|
||||
|
||||
offset = worldLayer.SeedOffset;
|
||||
noise.SetSeed(seed + offset);
|
||||
noise.SetFrequency(worldLayer.Frequency);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = (noise.GetCellular(indices.X, indices.Y) + 1f) / 2f;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
{
|
||||
DebugTools.Assert(value is <= 1f and >= 0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (layer is not BiomeEntityLayer biomeLayer)
|
||||
{
|
||||
entity = null;
|
||||
noise.SetFrequency(oldFrequency);
|
||||
noise.SetSeed(seed);
|
||||
return false;
|
||||
}
|
||||
|
||||
entity = Pick(biomeLayer.Entities, (noise.GetSimplex(indices.X, indices.Y) + 1f) / 2f);
|
||||
noise.SetFrequency(oldFrequency);
|
||||
noise.SetSeed(seed);
|
||||
return true;
|
||||
}
|
||||
|
||||
noise.SetFrequency(oldFrequency);
|
||||
noise.SetSeed(seed);
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the relevant decals for this tile.
|
||||
/// </summary>
|
||||
public bool TryGetDecals(Vector2i indices, BiomePrototype prototype, FastNoise noise, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, prototype, noise, grid, out var tileRef))
|
||||
{
|
||||
decals = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tileId = TileDefManager[tileRef.Value.TypeId].ID;
|
||||
var oldFrequency = noise.GetFrequency();
|
||||
var seed = noise.GetSeed();
|
||||
|
||||
for (var i = prototype.Layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = prototype.Layers[i];
|
||||
var offset = 0;
|
||||
|
||||
// Entities might block decal so need to check if there's one in front of us.
|
||||
switch (layer)
|
||||
{
|
||||
case IBiomeWorldLayer worldLayer:
|
||||
if (!worldLayer.AllowedTiles.Contains(tileId))
|
||||
continue;
|
||||
|
||||
offset = worldLayer.SeedOffset;
|
||||
noise.SetSeed(seed + offset);
|
||||
noise.SetFrequency(worldLayer.Frequency);
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the other layer should even render, if not then keep going.
|
||||
if (layer is not BiomeDecalLayer decalLayer)
|
||||
{
|
||||
if ((noise.GetCellular(indices.X, indices.Y) + 1f) / 2f < layer.Threshold)
|
||||
continue;
|
||||
|
||||
decals = null;
|
||||
noise.SetFrequency(oldFrequency);
|
||||
noise.SetSeed(seed);
|
||||
return false;
|
||||
}
|
||||
|
||||
decals = new List<(string ID, Vector2 Position)>();
|
||||
|
||||
for (var x = 0; x < decalLayer.Divisions; x++)
|
||||
{
|
||||
for (var y = 0; y < decalLayer.Divisions; y++)
|
||||
{
|
||||
var index = new Vector2(indices.X + x * 1f / decalLayer.Divisions, indices.Y + y * 1f / decalLayer.Divisions);
|
||||
var decalValue = (noise.GetCellular(index.X, index.Y) + 1f) / 2f;
|
||||
|
||||
if (decalValue < decalLayer.Threshold)
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(decalValue is <= 1f and >= 0f);
|
||||
decals.Add((Pick(decalLayer.Decals, (noise.GetSimplex(index.X, index.Y) + 1f) / 2f), index));
|
||||
}
|
||||
}
|
||||
|
||||
noise.SetFrequency(oldFrequency);
|
||||
noise.SetSeed(seed);
|
||||
|
||||
// Check other layers
|
||||
if (decals.Count == 0)
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
noise.SetFrequency(oldFrequency);
|
||||
noise.SetSeed(seed);
|
||||
decals = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
||||
/// </summary>
|
||||
public bool TryGetTile(Vector2i indices, FastNoise seed, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (threshold > 0f)
|
||||
{
|
||||
var found = (seed.GetSimplexFractal(indices.X, indices.Y) + 1f) / 2f;
|
||||
|
||||
if (found < threshold)
|
||||
{
|
||||
tile = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
byte variant = 0;
|
||||
var variantCount = variants?.Count ?? tileDef.Variants;
|
||||
|
||||
// Pick a variant tile if they're available as well
|
||||
if (variantCount > 1)
|
||||
{
|
||||
var variantValue = (seed.GetSimplex(indices.X * 2f, indices.Y * 2f) + 1f) / 2f;
|
||||
variant = (byte) Pick(variantCount, variantValue);
|
||||
|
||||
if (variants != null)
|
||||
{
|
||||
variant = variants[variant];
|
||||
}
|
||||
}
|
||||
|
||||
tile = new Tile(tileDef.TileId, 0, variant);
|
||||
return true;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
private sealed class BiomeComponentState : ComponentState
|
||||
{
|
||||
public int Seed;
|
||||
public string Prototype;
|
||||
|
||||
public BiomeComponentState(int seed, string prototype)
|
||||
{
|
||||
Seed = seed;
|
||||
Prototype = prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
cmd-planet-desc = Converts the supplied map into a planet with sensible defaults.
|
||||
cmd-planet-help = {$command} <mapid>.
|
||||
cmd-planet-args = Require 1 arg only.
|
||||
cmd-planet-args = Requires 2 args only.
|
||||
cmd-planet-map = Unable to parse {$map} as an existing map.
|
||||
cmd-planet-success = Set map {$mapId} to Planet. NOTE! You will need to load the map (either onto a new map or by restarting the game) for atmospherics to work.
|
||||
|
||||
@@ -69,6 +69,7 @@ tiles-green-circuit-floor = green circuit floor
|
||||
tiles-blue-circuit-floor = blue circuit floor
|
||||
tiles-snow = snow
|
||||
tiles-grass-floor = grass floor
|
||||
tiles-planet-grass-floor = grass floor
|
||||
tiles-jungle-grass-floor = jungle grass floor
|
||||
tiles-dark-grass-floor = dark grass floor
|
||||
tiles-light-grass-floor = light grass floor
|
||||
|
||||
73
Resources/Prototypes/Decals/planet.yml
Normal file
@@ -0,0 +1,73 @@
|
||||
# Flowers
|
||||
- type: decal
|
||||
id: FlowersBROne
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_flowers.rsi
|
||||
state: flowersbr1
|
||||
|
||||
- type: decal
|
||||
id: FlowersBRTwo
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_flowers.rsi
|
||||
state: flowersbr2
|
||||
|
||||
- type: decal
|
||||
id: FlowersBRThree
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_flowers.rsi
|
||||
state: flowersbr3
|
||||
|
||||
# Grass
|
||||
- type: decal
|
||||
id: BushAOne
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: busha1
|
||||
|
||||
- type: decal
|
||||
id: BushATwo
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: busha2
|
||||
|
||||
- type: decal
|
||||
id: BushAThree
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: busha3
|
||||
|
||||
- type: decal
|
||||
id: BushCOne
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: bushc1
|
||||
|
||||
- type: decal
|
||||
id: BushCTwo
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: bushc2
|
||||
|
||||
- type: decal
|
||||
id: BushCThree
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: bushc3
|
||||
|
||||
- type: decal
|
||||
id: BushDOne
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: bushd1
|
||||
|
||||
- type: decal
|
||||
id: BushDTwo
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: bushd2
|
||||
|
||||
- type: decal
|
||||
id: BushDThree
|
||||
sprite:
|
||||
sprite: /Textures/Decals/Flora/flora_bushes.rsi
|
||||
state: bushd3
|
||||
@@ -1,3 +1,6 @@
|
||||
- type: parallax
|
||||
id: Blank
|
||||
|
||||
- type: parallax
|
||||
id: Default
|
||||
layers:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
layers:
|
||||
- texture:
|
||||
!type:ImageParallaxTextureSource
|
||||
path: "/Textures/Tiles/Planet/grass.rsi/grass0.png"
|
||||
path: "/Textures/Tiles/Planet/Grass/grass.png"
|
||||
slowness: 0
|
||||
scale: "1, 1"
|
||||
shader: ""
|
||||
|
||||
@@ -1103,26 +1103,12 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
|
||||
- type: tile
|
||||
id: FloorSnow
|
||||
name: tiles-snow
|
||||
sprite: /Textures/Tiles/snow.png
|
||||
baseTurfs:
|
||||
- FloorDirt
|
||||
isSubfloor: true
|
||||
canCrowbar: false
|
||||
footstepSounds:
|
||||
collection: FootstepSnow
|
||||
friction: 0.20
|
||||
itemDrop: FloorTileItemSnow
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
|
||||
- type: tile
|
||||
id: FloorGrass
|
||||
name: tiles-grass-floor
|
||||
name: tiles-planet-grass-floor
|
||||
sprite: /Textures/Tiles/grass.png
|
||||
baseTurfs:
|
||||
- FloorDirt
|
||||
|
||||
89
Resources/Prototypes/Tiles/planet.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
# Desert
|
||||
- type: tile
|
||||
id: FloorDesert
|
||||
name: tiles-desert-floor
|
||||
sprite: /Textures/Tiles/Planet/Desert/desert.png
|
||||
variants: 6
|
||||
placementVariants: [0, 1, 2, 3, 4, 5]
|
||||
isSubfloor: true
|
||||
canCrowbar: false
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorLowDesert
|
||||
name: tiles-low-desert-floor
|
||||
sprite: /Textures/Tiles/Planet/Desert/low_desert.png
|
||||
variants: 6
|
||||
placementVariants: [0, 1, 2, 3, 4, 5]
|
||||
isSubfloor: true
|
||||
canCrowbar: false
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
# Grass
|
||||
- type: tile
|
||||
id: FloorPlanetGrass
|
||||
name: tiles-grass-planet-floor
|
||||
sprite: /Textures/Tiles/Planet/Grass/grass.png
|
||||
variants: 4
|
||||
placementVariants: [0, 1, 2, 3]
|
||||
cornerSprites:
|
||||
- /Textures/Tiles/Planet/Grass/single_edge.png
|
||||
cardinalSprites:
|
||||
- /Textures/Tiles/Planet/Grass/double_edge.png
|
||||
baseTurfs:
|
||||
- FloorDirt
|
||||
isSubfloor: true
|
||||
canCrowbar: false
|
||||
footstepSounds:
|
||||
collection: FootstepGrass
|
||||
friction: 0.30
|
||||
itemDrop: FloorTileItemGrass
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
# Lava
|
||||
- type: tile
|
||||
id: FloorBasalt
|
||||
name: tiles-basalt-floor
|
||||
sprite: /Textures/Tiles/Planet/basalt.png
|
||||
isSubfloor: true
|
||||
canCrowbar: false
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
# Snow
|
||||
- type: tile
|
||||
id: FloorSnow
|
||||
name: tiles-snow-floor
|
||||
sprite: /Textures/Tiles/Planet/Snow/snow.png
|
||||
variants: 13
|
||||
placementVariants: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
#cornerSprites:
|
||||
# - /Textures/Tiles/Planet/Snow/single_edge.png
|
||||
#cardinalSprites:
|
||||
# - /Textures/Tiles/Planet/Snow/double_edge.png
|
||||
isSubfloor: true
|
||||
canCrowbar: false
|
||||
footstepSounds:
|
||||
collection: FootstepSnow
|
||||
friction: 0.20
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
# Wasteland
|
||||
240
Resources/Prototypes/biomes.yml
Normal file
@@ -0,0 +1,240 @@
|
||||
# Desert
|
||||
# TODO: Water in grasslands
|
||||
# TODO: Water in desert / grass?
|
||||
- type: biome
|
||||
id: LowDesert
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.99
|
||||
frequency: 1
|
||||
seedOffset: 1
|
||||
allowedTiles:
|
||||
- FloorLowDesert
|
||||
entities:
|
||||
- FloraRockSolid01
|
||||
- FloraRockSolid02
|
||||
- FloraRockSolid03
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.9
|
||||
frequency: 0.2
|
||||
allowedTiles:
|
||||
- FloorLowDesert
|
||||
entities:
|
||||
- AsteroidRock
|
||||
- !type:BiomeTileLayer
|
||||
threshold: 0
|
||||
variants:
|
||||
- 0
|
||||
tile: FloorLowDesert
|
||||
- !type:BiomeTileLayer
|
||||
threshold: 0.6
|
||||
tile: FloorLowDesert
|
||||
frequency: 0.1
|
||||
|
||||
# Grass
|
||||
- type: biome
|
||||
id: Grasslands
|
||||
layers:
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
seedOffset: 3
|
||||
threshold: 0.98
|
||||
divisions: 1
|
||||
frequency: 1
|
||||
decals:
|
||||
- FlowersBROne
|
||||
- FlowersBRTwo
|
||||
- FlowersBRThree
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
seedOffset: 2
|
||||
threshold: 0.95
|
||||
divisions: 2
|
||||
frequency: 1
|
||||
decals:
|
||||
- BushDOne
|
||||
- BushDTwo
|
||||
- BushDThree
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
seedOffset: 1
|
||||
threshold: 0.8
|
||||
divisions: 1
|
||||
frequency: 0.05
|
||||
decals:
|
||||
- BushCOne
|
||||
- BushCTwo
|
||||
- BushCThree
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
divisions: 1
|
||||
decals:
|
||||
- BushAOne
|
||||
- BushATwo
|
||||
- BushAThree
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.9
|
||||
frequency: 1
|
||||
allowedTiles:
|
||||
- FloorPlanetGrass
|
||||
entities:
|
||||
- FloraTree01
|
||||
- FloraTree02
|
||||
- FloraTree03
|
||||
- FloraTree04
|
||||
- FloraTree05
|
||||
- FloraTree06
|
||||
- FloraTreeLarge01
|
||||
- FloraTreeLarge02
|
||||
- FloraTreeLarge03
|
||||
- FloraTreeLarge04
|
||||
- FloraTreeLarge05
|
||||
- FloraTreeLarge06
|
||||
# Fill remainder with sand.
|
||||
- !type:BiomeTileLayer
|
||||
threshold: 0
|
||||
tile: FloorAsteroidSand
|
||||
- !type:BiomeTileLayer
|
||||
threshold: 0.5
|
||||
tile: FloorPlanetGrass
|
||||
|
||||
# Lava
|
||||
- type: biome
|
||||
id: Lava
|
||||
layers:
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.9
|
||||
frequency: 1
|
||||
seedOffset: 3
|
||||
allowedTiles:
|
||||
- FloorBasalt
|
||||
entities:
|
||||
- BasaltOne
|
||||
- BasaltTwo
|
||||
- BasaltThree
|
||||
- BasaltFour
|
||||
- BasaltFive
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorBasalt
|
||||
seedOffset: 2
|
||||
threshold: 0.9
|
||||
divisions: 1
|
||||
frequency: 1
|
||||
decals:
|
||||
- Basalt1
|
||||
- Basalt2
|
||||
- Basalt3
|
||||
- Basalt4
|
||||
- Basalt5
|
||||
- Basalt6
|
||||
- Basalt7
|
||||
- Basalt8
|
||||
- Basalt9
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.99
|
||||
frequency: 1
|
||||
seedOffset: 1
|
||||
allowedTiles:
|
||||
- FloorBasalt
|
||||
entities:
|
||||
- FloraRockSolid01
|
||||
- FloraRockSolid02
|
||||
- FloraRockSolid03
|
||||
- !type:BiomeEntityLayer
|
||||
threshold: 0.7
|
||||
frequency: 0.2
|
||||
allowedTiles:
|
||||
- FloorBasalt
|
||||
entities:
|
||||
- FloorLavaEntity
|
||||
- !type:BiomeTileLayer
|
||||
threshold: 0
|
||||
variants:
|
||||
- 0
|
||||
tile: FloorBasalt
|
||||
|
||||
# Snow
|
||||
- type: biome
|
||||
id: Snow
|
||||
layers:
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorSnow
|
||||
seedOffset: 4
|
||||
threshold: 0.95
|
||||
divisions: 1
|
||||
frequency: 1
|
||||
decals:
|
||||
- grasssnowa1
|
||||
- grasssnowa2
|
||||
- grasssnowa3
|
||||
- grasssnowb1
|
||||
- grasssnowb2
|
||||
- grasssnowb3
|
||||
- grasssnowc1
|
||||
- grasssnowc2
|
||||
- grasssnowc3
|
||||
# The main grass texture, this one blends in very well
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorSnow
|
||||
divisions: 1
|
||||
seedOffset: 3
|
||||
threshold: 0.8
|
||||
frequency: 0.05
|
||||
decals:
|
||||
- grasssnow
|
||||
- grasssnow01
|
||||
- grasssnow02
|
||||
- grasssnow03
|
||||
- grasssnow04
|
||||
- grasssnow05
|
||||
- grasssnow06
|
||||
- grasssnow07
|
||||
- grasssnow08
|
||||
- grasssnow09
|
||||
- grasssnow10
|
||||
- grasssnow11
|
||||
- grasssnow12
|
||||
- grasssnow13
|
||||
- !type:BiomeDecalLayer
|
||||
allowedTiles:
|
||||
- FloorSnow
|
||||
seedOffset: 2
|
||||
threshold: 0.99
|
||||
divisions: 1
|
||||
frequency: 1
|
||||
decals:
|
||||
- bushsnowa1
|
||||
- bushsnowa2
|
||||
- bushsnowa3
|
||||
- bushsnowb1
|
||||
- bushsnowb2
|
||||
- bushsnowb3
|
||||
- !type:BiomeEntityLayer
|
||||
seedOffset: 1
|
||||
threshold: 0.95
|
||||
frequency: 1
|
||||
allowedTiles:
|
||||
- FloorSnow
|
||||
entities:
|
||||
- FloraTreeSnow01
|
||||
- FloraTreeSnow02
|
||||
- FloraTreeSnow03
|
||||
- FloraTreeSnow04
|
||||
- FloraTreeSnow05
|
||||
- FloraTreeSnow06
|
||||
- !type:BiomeTileLayer
|
||||
threshold: 0
|
||||
variants:
|
||||
- 0
|
||||
tile: FloorSnow
|
||||
- !type:BiomeTileLayer
|
||||
threshold: 0.6
|
||||
tile: FloorSnow
|
||||
frequency: 0.1
|
||||
BIN
Resources/Textures/Tiles/Asteroid/asteroid_dug.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Resources/Textures/Tiles/Planet/Desert/desert.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
Resources/Textures/Tiles/Planet/Desert/desert_dug.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Resources/Textures/Tiles/Planet/Desert/low_desert.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
Resources/Textures/Tiles/Planet/Desert/low_desert_dug.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
53
Resources/Textures/Tiles/Planet/Desert/meta.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "https://github.com/discordia-space/CEV-Eris/tree/d1b3041899a42ef1fb59cd7ad4a83a300b35638c",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "desert"
|
||||
},
|
||||
{
|
||||
"name": "desert_dug"
|
||||
},
|
||||
{
|
||||
"name": "desert0"
|
||||
},
|
||||
{
|
||||
"name": "desert1"
|
||||
},
|
||||
{
|
||||
"name": "desert2"
|
||||
},
|
||||
{
|
||||
"name": "desert3"
|
||||
},
|
||||
{
|
||||
"name": "desert4"
|
||||
},
|
||||
{
|
||||
"name": "lowdesert"
|
||||
},
|
||||
{
|
||||
"name": "lowdesert_dug"
|
||||
},
|
||||
{
|
||||
"name": "lowdesert0"
|
||||
},
|
||||
{
|
||||
"name": "lowdesert1"
|
||||
},
|
||||
{
|
||||
"name": "lowdesert2"
|
||||
},
|
||||
{
|
||||
"name": "lowdesert3"
|
||||
},
|
||||
{
|
||||
"name": "lowdesert4"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
Resources/Textures/Tiles/Planet/Grass/attributions.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
- files:
|
||||
- grass.png
|
||||
- double_edge.png
|
||||
- single_edge.png
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "https://github.com/discordia-space/CEV-Eris/commit/026ee3250ac1de938b503e3eb46ad73dd9c3ca82"
|
||||
source: "https://github.com/discordia-space/CEV-Eris/commit/026ee3250ac1de938b503e3eb46ad73dd9c3ca82"
|
||||
BIN
Resources/Textures/Tiles/Planet/Grass/double_edge.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
Resources/Textures/Tiles/Planet/Grass/grass.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Resources/Textures/Tiles/Planet/Grass/single_edge.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
Resources/Textures/Tiles/Planet/Grass/triple_edge.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/edge0.png
Normal file
|
After Width: | Height: | Size: 483 B |
BIN
Resources/Textures/Tiles/Planet/Snow/edge1.png
Normal file
|
After Width: | Height: | Size: 637 B |
BIN
Resources/Textures/Tiles/Planet/Snow/edge2.png
Normal file
|
After Width: | Height: | Size: 487 B |
BIN
Resources/Textures/Tiles/Planet/Snow/gravsnow.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/gravsnow_corner.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/gravsnow_surround.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/ice.png
Normal file
|
After Width: | Height: | Size: 293 B |
101
Resources/Textures/Tiles/Planet/Snow/meta.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "snow"
|
||||
},
|
||||
{
|
||||
"name": "snow_corner",
|
||||
"directions": 8
|
||||
},
|
||||
{
|
||||
"name": "snow_surround",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "gravsnow"
|
||||
},
|
||||
{
|
||||
"name": "gravsnow_corner",
|
||||
"directions": 8
|
||||
},
|
||||
{
|
||||
"name": "gravsnow_surround",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "plating"
|
||||
},
|
||||
{
|
||||
"name": "platingdrift",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "ice"
|
||||
},
|
||||
{
|
||||
"name": "snowwhite"
|
||||
},
|
||||
{
|
||||
"name": "snow0"
|
||||
},
|
||||
{
|
||||
"name": "snow1"
|
||||
},
|
||||
{
|
||||
"name": "snow2"
|
||||
},
|
||||
{
|
||||
"name": "snow3"
|
||||
},
|
||||
{
|
||||
"name": "snow4"
|
||||
},
|
||||
{
|
||||
"name": "snow5"
|
||||
},
|
||||
{
|
||||
"name": "snow6"
|
||||
},
|
||||
{
|
||||
"name": "snow7"
|
||||
},
|
||||
{
|
||||
"name": "snow8"
|
||||
},
|
||||
{
|
||||
"name": "snow9"
|
||||
},
|
||||
{
|
||||
"name": "snow10"
|
||||
},
|
||||
{
|
||||
"name": "snow11"
|
||||
},
|
||||
{
|
||||
"name": "snow12"
|
||||
},
|
||||
{
|
||||
"name": "snowplating"
|
||||
},
|
||||
{
|
||||
"name": "permafrost"
|
||||
},
|
||||
{
|
||||
"name": "edge0",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "edge1",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "edge2",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/Tiles/Planet/Snow/permafrost.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/plating.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/platingdrift.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/snow.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/snow_corner.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/snow_piss.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/snow_surround.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/snowplating.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Resources/Textures/Tiles/Planet/Snow/snowwhite.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 706 B |
|
Before Width: | Height: | Size: 692 B |
|
Before Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 608 B |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "https://github.com/discordia-space/CEV-Eris/commit/026ee3250ac1de938b503e3eb46ad73dd9c3ca82",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "grass0"
|
||||
},
|
||||
{
|
||||
"name": "grass1"
|
||||
},
|
||||
{
|
||||
"name": "grass2"
|
||||
},
|
||||
{
|
||||
"name": "grass3"
|
||||
},
|
||||
{
|
||||
"name": "grass_edges",
|
||||
"directions": 8
|
||||
},
|
||||
{
|
||||
"name": "grass_edges_old",
|
||||
"directions": 8
|
||||
},
|
||||
{
|
||||
"name": "grass_corners",
|
||||
"directions": 8
|
||||
},
|
||||
{
|
||||
"name": "grass_edge_corner",
|
||||
"directions": 8
|
||||
}
|
||||
]
|
||||
}
|
||||