using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Client.IconSmoothing
{
--- /dev/null
+using Content.Shared.Parallax.Biomes;
+
+namespace Content.Client.Parallax;
+
+public sealed class BiomeSystem : SharedBiomeSystem
+{
+
+}
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;
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;
{
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)
{
public sealed class TilePainter
{
- private const string TilesPath = "/Textures/Tiles/";
public const int TileImageSize = EyeManager.PixelsPerMeter;
private readonly ITileDefinitionManager _sTileDefinitionManager;
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;
{
[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;
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;
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;
}
}
--- /dev/null
+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);
+ }
+ }
+}
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();
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;
[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!;
}
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;
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))
{
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;
}
--- /dev/null
+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();
+}
--- /dev/null
+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();
+}
--- /dev/null
+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;
+ }
+ }
+}
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.
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
--- /dev/null
+# 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
+- type: parallax
+ id: Blank
+
- type: parallax
id: Default
layers:
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: ""
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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+{
+ "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"
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+- 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"
\ No newline at end of file
--- /dev/null
+{
+ "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
+ }
+ ]
+}
\ No newline at end of file
+++ /dev/null
-{
- "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
- }
- ]
-}
\ No newline at end of file