Per-map parallax support (#9786)

* Per-map parallax support

* Comments for future sloth

* c

* bet

* Fix exception

* VV support

* Fix parallax

* mem

* weightless sounds

* Gravity stuff

* placeholder coz im too lazy to stash don't @ me son

* decent clouds

* sky

* Fast parallax

* Imagine spelling

* Loicense

* perish

* Fix weightless status

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2022-07-25 15:10:23 +10:00
committed by GitHub
parent aad6a22a6a
commit bfac53e7bc
36 changed files with 607 additions and 412 deletions

View File

@@ -182,10 +182,10 @@ namespace Content.Client.Entry
ContentContexts.SetupContexts(inputMan.Contexts);
IoCManager.Resolve<IGameHud>().Initialize();
IoCManager.Resolve<IParallaxManager>().LoadParallax(); // Have to do this later because prototypes are needed.
IoCManager.Resolve<IParallaxManager>().LoadDefaultParallax(); // Have to do this later because prototypes are needed.
var overlayMgr = IoCManager.Resolve<IOverlayManager>();
overlayMgr.AddOverlay(new ParallaxOverlay());
overlayMgr.AddOverlay(new SingularityOverlay());
overlayMgr.AddOverlay(new FlashOverlay());
overlayMgr.AddOverlay(new RadiationPulseOverlay());

View File

@@ -2,7 +2,7 @@ using Content.Shared.Gravity;
namespace Content.Client.Gravity
{
internal sealed class GravitySystem : SharedGravitySystem
public sealed class GravitySystem : SharedGravitySystem
{
}

View File

@@ -1,7 +1,5 @@
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Client.Graphics;
namespace Content.Client.Parallax.Data

View File

@@ -1,12 +1,9 @@
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using JetBrains.Annotations;
using Content.Client.Resources;
using Content.Client.IoC;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Content.Client.Parallax.Data;

View File

@@ -62,5 +62,12 @@ public sealed class ParallaxLayerConfig
/// </summary>
[DataField("slowness")]
public float Slowness { get; set; } = 0.5f;
/// <summary>
/// Should the parallax scroll at a specific rate per second.
/// </summary>
[DataField("scrolling")] public Vector2 Scrolling = Vector2.Zero;
[DataField("shader")] public string? Shader = "unshaded";
}

View File

@@ -1,35 +1,30 @@
using System;
using Robust.Client.Graphics;
using Content.Client.Parallax;
using System.Threading.Tasks;
using Robust.Shared.Maths;
namespace Content.Client.Parallax.Managers;
public interface IParallaxManager
{
/// <summary>
/// The current parallax.
/// Changing this causes a new parallax to be loaded (eventually).
/// Do not alter until prototype manager is available.
/// Useful "csi" input for testing new parallaxes:
/// using Content.Client.Parallax.Managers; IoCManager.Resolve<IParallaxManager>().ParallaxName = "test";
/// </summary>
string ParallaxName { get; set; }
/// <summary>
/// All WorldHomePosition values are offset by this.
/// </summary>
Vector2 ParallaxAnchor { get; set; }
/// <summary>
/// The layers of the currently loaded parallax.
/// This will change on a whim without notification.
/// </summary>
ParallaxLayerPrepared[] ParallaxLayers { get; }
bool IsLoaded(string name);
/// <summary>
/// Used to initialize the manager.
/// The layers of the selected parallax.
/// </summary>
ParallaxLayerPrepared[] GetParallaxLayers(string name);
/// <summary>
/// Loads in the default parallax to use.
/// Do not call until prototype manager is available.
/// </summary>
void LoadParallax();
void LoadDefaultParallax();
Task LoadParallaxByName(string name);
void UnloadParallax(string name);
}

View File

@@ -1,111 +1,103 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Content.Client.Parallax.Data;
using Content.Shared;
using Content.Shared.CCVar;
using Nett;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Prototypes;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace Content.Client.Parallax.Managers;
internal sealed class ParallaxManager : IParallaxManager
public sealed class ParallaxManager : IParallaxManager
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
private string _parallaxName = "";
public string ParallaxName
{
get => _parallaxName;
set
{
LoadParallaxByName(value);
}
}
private ISawmill _sawmill = Logger.GetSawmill("parallax");
public Vector2 ParallaxAnchor { get; set; }
private CancellationTokenSource? _presentParallaxLoadCancel;
private readonly ConcurrentDictionary<string, ParallaxLayerPrepared[]> _parallaxesLQ = new();
private readonly ConcurrentDictionary<string, ParallaxLayerPrepared[]> _parallaxesHQ = new();
private ParallaxLayerPrepared[] _parallaxLayersHQ = {};
private ParallaxLayerPrepared[] _parallaxLayersLQ = {};
private readonly ConcurrentDictionary<string, CancellationTokenSource> _loadingParallaxes = new();
public ParallaxLayerPrepared[] ParallaxLayers => _configurationManager.GetCVar(CCVars.ParallaxLowQuality) ? _parallaxLayersLQ : _parallaxLayersHQ;
public bool IsLoaded(string name) => _parallaxesLQ.ContainsKey(name);
public async void LoadParallax()
public ParallaxLayerPrepared[] GetParallaxLayers(string name)
{
await LoadParallaxByName("default");
if (_configurationManager.GetCVar(CCVars.ParallaxLowQuality))
{
return !_parallaxesLQ.TryGetValue(name, out var lq) ? Array.Empty<ParallaxLayerPrepared>() : lq;
}
private async Task LoadParallaxByName(string name)
return !_parallaxesLQ.TryGetValue(name, out var hq) ? Array.Empty<ParallaxLayerPrepared>() : hq;
}
public void UnloadParallax(string name)
{
// Update _parallaxName
if (_parallaxName == name)
if (_loadingParallaxes.TryGetValue(name, out var loading))
{
loading.Cancel();
_loadingParallaxes.Remove(name, out _);
return;
}
_parallaxName = name;
if (!_parallaxesLQ.ContainsKey(name)) return;
_parallaxesLQ.Remove(name, out _);
_parallaxesHQ.Remove(name, out _);
}
public async void LoadDefaultParallax()
{
await LoadParallaxByName("Default");
}
public async Task LoadParallaxByName(string name)
{
if (_parallaxesLQ.ContainsKey(name) || _loadingParallaxes.ContainsKey(name)) return;
// Cancel any existing load and setup the new cancellation token
_presentParallaxLoadCancel?.Cancel();
_presentParallaxLoadCancel = new CancellationTokenSource();
var cancel = _presentParallaxLoadCancel.Token;
// Empty parallax name = no layers (this is so that the initial "" parallax name is consistent)
if (_parallaxName == "")
{
_parallaxLayersHQ = _parallaxLayersLQ = new ParallaxLayerPrepared[] {};
return;
}
var token = new CancellationTokenSource();
_loadingParallaxes[name] = token;
var cancel = token.Token;
// Begin (for real)
Logger.InfoS("parallax", $"Loading parallax {name}");
_sawmill.Info($"Loading parallax {name}");
try
{
var parallaxPrototype = _prototypeManager.Index<ParallaxPrototype>(name);
ParallaxLayerPrepared[] hq;
ParallaxLayerPrepared[] lq;
ParallaxLayerPrepared[][] layers;
if (parallaxPrototype.LayersLQUseHQ)
{
lq = hq = await LoadParallaxLayers(parallaxPrototype.Layers, cancel);
layers = new ParallaxLayerPrepared[2][];
layers[0] = layers[1] = await LoadParallaxLayers(parallaxPrototype.Layers, cancel);
}
else
{
var results = await Task.WhenAll(
layers = await Task.WhenAll(
LoadParallaxLayers(parallaxPrototype.Layers, cancel),
LoadParallaxLayers(parallaxPrototype.LayersLQ, cancel)
);
hq = results[0];
lq = results[1];
}
// Still keeping this check just in case.
if (_parallaxName == name)
{
_parallaxLayersHQ = hq;
_parallaxLayersLQ = lq;
Logger.InfoS("parallax", $"Loaded parallax {name}");
}
_loadingParallaxes.Remove(name, out _);
if (token.Token.IsCancellationRequested) return;
_parallaxesLQ[name] = layers[0];
_parallaxesHQ[name] = layers[1];
_sawmill.Info($"Loaded parallax {name}");
}
catch (Exception ex)
{
Logger.ErrorS("parallax", $"Failed to loaded parallax {name}: {ex}");
_sawmill.Error($"Failed to loaded parallax {name}: {ex}");
}
}

View File

@@ -1,10 +1,7 @@
using Content.Client.Parallax.Managers;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.ViewVariables;
namespace Content.Client.Parallax;
@@ -28,7 +25,7 @@ public sealed class ParallaxControl : Control
protected override void Draw(DrawingHandleScreen handle)
{
foreach (var layer in _parallaxManager.ParallaxLayers)
foreach (var layer in _parallaxManager.GetParallaxLayers("Default"))
{
var tex = layer.Texture;
var texSize = tex.Size * layer.Config.Scale.Floored();

View File

@@ -1,47 +1,55 @@
using System;
using Content.Client.Parallax.Managers;
using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Parallax;
public sealed class ParallaxOverlay : Overlay
{
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IParallaxManager _manager = default!;
private readonly ParallaxSystem _parallax;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader;
public ParallaxOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
_parallax = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ParallaxSystem>();
}
protected override void Draw(in OverlayDrawArgs args)
{
if (args.Viewport.Eye == null)
{
if (args.MapId == MapId.Nullspace)
return;
}
if (!_configurationManager.GetCVar(CCVars.ParallaxEnabled))
{
return;
}
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
var screenHandle = args.WorldHandle;
screenHandle.UseShader(_shader);
foreach (var layer in _parallaxManager.ParallaxLayers)
var layers = _parallax.GetParallaxLayers(args.MapId);
var realTime = (float) _timing.RealTime.TotalSeconds;
foreach (var layer in layers)
{
ShaderInstance? shader;
if (!string.IsNullOrEmpty(layer.Config.Shader))
shader = _prototypeManager.Index<ShaderPrototype>(layer.Config.Shader).Instance();
else
shader = null;
screenHandle.UseShader(shader);
var tex = layer.Texture;
// Size of the texture in world units.
@@ -52,10 +60,11 @@ public sealed class ParallaxOverlay : Overlay
// The effects of this are such that a slowness of 1 anchors the layer to the centre of the screen, while a slowness of 0 anchors the layer to the world.
// (For values 0.0 to 1.0 this is in effect a lerp, but it's deliberately unclamped.)
// The ParallaxAnchor adapts the parallax for station positioning and possibly map-specific tweaks.
var home = layer.Config.WorldHomePosition + _parallaxManager.ParallaxAnchor;
var home = layer.Config.WorldHomePosition + _manager.ParallaxAnchor;
var scrolled = layer.Config.Scrolling * realTime;
// Origin - start with the parallax shift itself.
var originBL = (args.Viewport.Eye.Position.Position - home) * layer.Config.Slowness;
var originBL = (position - home) * layer.Config.Slowness + scrolled;
// Place at the home.
originBL += home;
@@ -90,6 +99,8 @@ public sealed class ParallaxOverlay : Overlay
screenHandle.DrawTextureRect(tex, Box2.FromDimensions(originBL, size));
}
}
screenHandle.UseShader(null);
}
}

View File

@@ -0,0 +1,71 @@
using Content.Client.Parallax.Managers;
using Content.Shared.Parallax;
using Robust.Client.Graphics;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.Client.Parallax;
public sealed class ParallaxSystem : SharedParallaxSystem
{
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly IParallaxManager _parallax = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private const string Fallback = "Default";
public override void Initialize()
{
base.Initialize();
_overlay.AddOverlay(new ParallaxOverlay());
SubscribeLocalEvent<ParallaxComponent, ComponentHandleState>(OnParallaxHandleState);
_protoManager.PrototypesReloaded += OnReload;
}
private void OnReload(PrototypesReloadedEventArgs obj)
{
_parallax.UnloadParallax(Fallback);
_parallax.LoadDefaultParallax();
foreach (var comp in EntityQuery<ParallaxComponent>(true))
{
_parallax.UnloadParallax(comp.Parallax);
_parallax.LoadParallaxByName(comp.Parallax);
}
}
public override void Shutdown()
{
base.Shutdown();
_overlay.RemoveOverlay<ParallaxOverlay>();
_protoManager.PrototypesReloaded -= OnReload;
}
private void OnParallaxHandleState(EntityUid uid, ParallaxComponent component, ref ComponentHandleState args)
{
if (args.Current is not ParallaxComponentState state) return;
component.Parallax = state.Parallax;
if (!_parallax.IsLoaded(component.Parallax))
{
_parallax.LoadParallaxByName(component.Parallax);
}
}
public ParallaxLayerPrepared[] GetParallaxLayers(MapId mapId)
{
return _parallax.GetParallaxLayers(GetParallax(_map.GetMapEntityId(mapId)));
}
public string GetParallax(MapId mapId)
{
return GetParallax(_map.GetMapEntityId(mapId));
}
public string GetParallax(EntityUid mapUid)
{
return TryComp<ParallaxComponent>(mapUid, out var parallax) ? parallax.Parallax : Fallback;
}
}

View File

@@ -1,3 +1,5 @@
using System;
using System.Threading.Tasks;
using Content.Client.Parallax.Managers;
using Content.Client.Parallax;
using Robust.Shared.Maths;
@@ -6,13 +8,30 @@ namespace Content.IntegrationTests
{
public sealed class DummyParallaxManager : IParallaxManager
{
public string ParallaxName { get; set; } = "";
public Vector2 ParallaxAnchor { get; set; }
public ParallaxLayerPrepared[] ParallaxLayers { get; } = {};
public void LoadParallax()
public bool IsLoaded(string name)
{
ParallaxName = "default";
return true;
}
public ParallaxLayerPrepared[] GetParallaxLayers(string name)
{
return Array.Empty<ParallaxLayerPrepared>();
}
public void LoadDefaultParallax()
{
return;
}
public Task LoadParallaxByName(string name)
{
return Task.CompletedTask;
}
public void UnloadParallax(string name)
{
return;
}
}
}

View File

@@ -11,7 +11,7 @@ using Robust.Shared.Map;
namespace Content.IntegrationTests.Tests.Gravity
{
[TestFixture]
[TestOf(typeof(WeightlessSystem))]
[TestOf(typeof(GravitySystem))]
[TestOf(typeof(GravityGeneratorComponent))]
public sealed class WeightlessStatusTests
{
@@ -21,6 +21,9 @@ namespace Content.IntegrationTests.Tests.Gravity
id: HumanDummy
components:
- type: Alerts
- type: Physics
bodyType: Dynamic
- type: entity
name: GravityGeneratorDummy
id: GravityGeneratorDummy
@@ -38,7 +41,6 @@ namespace Content.IntegrationTests.Tests.Gravity
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = Prototypes});
var server = pairTracker.Pair.Server;
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var alertsSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<AlertsSystem>();

View File

@@ -70,8 +70,8 @@ namespace Content.IntegrationTests.Tests
var grid1Entity = grid1.GridEntityId;
var grid2Entity = grid2.GridEntityId;
Assert.That(!entityMan.GetComponent<GravityComponent>(grid1Entity).Enabled);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2Entity).Enabled);
Assert.That(!entityMan.GetComponent<GravityComponent>(grid1Entity).EnabledVV);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2Entity).EnabledVV);
// Re-enable needs power so it turns off again.
// Charge rate is ridiculously high so it finishes in one tick.
@@ -88,7 +88,7 @@ namespace Content.IntegrationTests.Tests
var grid2Entity = grid2.GridEntityId;
Assert.That(entityMan.GetComponent<GravityComponent>(grid2Entity).Enabled, Is.False);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2Entity).EnabledVV, Is.False);
});
await pairTracker.CleanReturnAsync();

View File

@@ -4,16 +4,16 @@ using JetBrains.Annotations;
namespace Content.Server.Gravity.EntitySystems
{
[UsedImplicitly]
internal sealed class GravitySystem : SharedGravitySystem
public sealed class GravitySystem : SharedGravitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GravityComponent, ComponentInit>(HandleGravityInitialize);
SubscribeLocalEvent<GravityComponent, ComponentShutdown>(HandleGravityShutdown);
SubscribeLocalEvent<GravityComponent, ComponentInit>(OnGravityInit);
SubscribeLocalEvent<GravityComponent, ComponentShutdown>(OnGravityShutdown);
}
private void HandleGravityInitialize(EntityUid uid, GravityComponent component, ComponentInit args)
private void OnGravityInit(EntityUid uid, GravityComponent component, ComponentInit args)
{
// Incase there's already a generator on the grid we'll just set it now.
var gridId = Transform(component.Owner).GridUid;
@@ -21,25 +21,25 @@ namespace Content.Server.Gravity.EntitySystems
if (gridId == null)
return;
GravityChangedMessage message;
GravityChangedEvent message;
foreach (var generator in EntityManager.EntityQuery<GravityGeneratorComponent>())
{
if (Transform(generator.Owner).GridUid == gridId && generator.GravityActive)
{
component.Enabled = true;
message = new GravityChangedMessage(gridId.Value, true);
message = new GravityChangedEvent(gridId.Value, true);
RaiseLocalEvent(message);
return;
}
}
component.Enabled = false;
message = new GravityChangedMessage(gridId.Value, false);
message = new GravityChangedEvent(gridId.Value, false);
RaiseLocalEvent(message);
}
private void HandleGravityShutdown(EntityUid uid, GravityComponent component, ComponentShutdown args)
private void OnGravityShutdown(EntityUid uid, GravityComponent component, ComponentShutdown args)
{
DisableGravity(component);
}
@@ -53,7 +53,7 @@ namespace Content.Server.Gravity.EntitySystems
return;
comp.Enabled = true;
var message = new GravityChangedMessage(gridId.Value, true);
var message = new GravityChangedEvent(gridId.Value, true);
RaiseLocalEvent(message);
}
@@ -66,7 +66,7 @@ namespace Content.Server.Gravity.EntitySystems
if (gridId == null)
return;
var message = new GravityChangedMessage(gridId.Value, false);
var message = new GravityChangedEvent(gridId.Value, false);
RaiseLocalEvent(message);
}
}

View File

@@ -1,157 +0,0 @@
using Content.Shared.Alert;
using Content.Shared.GameTicking;
using Content.Shared.Gravity;
using Content.Shared.Movement.Components;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.Server.Gravity.EntitySystems
{
[UsedImplicitly]
public sealed class WeightlessSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
private readonly Dictionary<EntityUid, List<AlertsComponent>> _alerts = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<GravityChangedMessage>(GravityChanged);
SubscribeLocalEvent<AlertsComponent, EntParentChangedMessage>(EntParentChanged);
SubscribeLocalEvent<AlertsComponent, AlertSyncEvent>(HandleAlertSyncEvent);
}
public void Reset(RoundRestartCleanupEvent ev)
{
_alerts.Clear();
}
public void AddAlert(AlertsComponent status)
{
var xform = Transform(status.Owner);
if (xform.GridUid != null)
{
var alerts = _alerts.GetOrNew(xform.GridUid.Value);
alerts.Add(status);
}
if (_mapManager.TryGetGrid(xform.GridUid, out var grid))
{
var alerts = _alerts.GetOrNew(xform.GridUid.Value);
alerts.Add(status);
if (EntityManager.GetComponent<GravityComponent>(grid.GridEntityId).Enabled)
{
RemoveWeightless(status.Owner);
}
else
{
AddWeightless(status.Owner);
}
}
else
{
AddWeightless(status.Owner);
}
}
public void RemoveAlert(AlertsComponent status)
{
var grid = EntityManager.GetComponent<TransformComponent>(status.Owner).GridUid;
if (grid == null || !_alerts.TryGetValue(grid.Value, out var statuses))
{
return;
}
statuses.Remove(status);
}
private void GravityChanged(GravityChangedMessage ev)
{
if (!_alerts.TryGetValue(ev.ChangedGridIndex, out var statuses))
{
return;
}
if (ev.HasGravity)
{
foreach (var status in statuses)
{
RemoveWeightless(status.Owner);
}
}
else
{
foreach (var status in statuses)
{
AddWeightless(status.Owner);
}
}
}
private void AddWeightless(EntityUid euid)
{
_alertsSystem.ShowAlert(euid, AlertType.Weightless);
}
private void RemoveWeightless(EntityUid euid)
{
_alertsSystem.ClearAlert(euid, AlertType.Weightless);
}
private void EntParentChanged(EntityUid uid, AlertsComponent status, ref EntParentChangedMessage ev)
{
// First, update the `_alerts` dictionary
if (ev.OldParent is {Valid: true} old &&
EntityManager.TryGetComponent(old, out IMapGridComponent? mapGrid))
{
var oldGrid = mapGrid.Owner;
if (_alerts.TryGetValue(oldGrid, out var oldStatuses))
{
oldStatuses.Remove(status);
}
}
if (ev.Transform.MapID == MapId.Nullspace)
return;
var newGrid = ev.Transform.GridUid;
if (newGrid != null)
{
var newStatuses = _alerts.GetOrNew(newGrid.Value);
newStatuses.Add(status);
}
// then update the actual alert. The alert is only removed if either the player is on a grid with gravity,
// or if they ignore gravity-based movement altogether.
// TODO: update this when planets and the like are added.
// TODO: update alert when the ignore-gravity component is added or removed.
if (_mapManager.TryGetGrid(newGrid, out var grid)
&& TryComp(grid.GridEntityId, out GravityComponent? gravity)
&& gravity.Enabled)
RemoveWeightless(status.Owner);
else if (!HasComp<MovementIgnoreGravityComponent>(uid))
AddWeightless(status.Owner);
}
private void HandleAlertSyncEvent(EntityUid uid, AlertsComponent component, AlertSyncEvent args)
{
switch (component.LifeStage)
{
case ComponentLifeStage.Starting:
AddAlert(component);
break;
case ComponentLifeStage.Removing:
RemoveAlert(component);
break;
}
}
}
}

View File

@@ -0,0 +1,23 @@
using Content.Shared.Parallax;
using Robust.Shared.GameStates;
namespace Content.Server.Parallax;
public sealed class ParallaxSystem : SharedParallaxSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ParallaxComponent, ComponentGetState>(OnParallaxGetState);
}
private void OnParallaxGetState(EntityUid uid, ParallaxComponent component, ref ComponentGetState args)
{
args.State = new ParallaxComponentState
{
Parallax = component.Parallax
};
}
}

View File

@@ -5,6 +5,7 @@ using Content.Server.Doors.Systems;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
using Content.Server.Stunnable;
using Content.Shared.Parallax;
using Content.Shared.Shuttles.Systems;
using Content.Shared.Sound;
using Content.Shared.StatusEffect;
@@ -195,6 +196,8 @@ public sealed partial class ShuttleSystem
component = AddComp<FTLComponent>(uid);
// TODO: Need BroadcastGrid to not be bad.
SoundSystem.Play(_startupSound.GetSound(), Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(component.Owner)), _startupSound.Params);
// Make sure the map is setup before we leave to avoid pop-in (e.g. parallax).
SetupHyperspace();
return true;
}
@@ -217,7 +220,6 @@ public sealed partial class ShuttleSystem
DoTheDinosaur(xform);
comp.State = FTLState.Travelling;
SetupHyperspace();
var width = Comp<IMapGridComponent>(comp.Owner).Grid.LocalAABB.Width;
xform.Coordinates = new EntityCoordinates(_mapManager.GetMapEntityId(_hyperSpaceMap!.Value), new Vector2(_index + width / 2f, 0f));
@@ -352,6 +354,8 @@ public sealed partial class ShuttleSystem
_hyperSpaceMap = _mapManager.CreateMap();
_sawmill.Info($"Setup hyperspace map at {_hyperSpaceMap.Value}");
DebugTools.Assert(!_mapManager.IsMapPaused(_hyperSpaceMap.Value));
var parallax = EnsureComp<ParallaxComponent>(_mapManager.GetMapEntityId(_hyperSpaceMap.Value));
parallax.Parallax = "FastSpace";
}
private void CleanupHyperspace()

View File

@@ -25,6 +25,7 @@ namespace Content.Shared.Friction
private float _stopSpeed;
private float _frictionModifier;
private const float DefaultFriction = 0.3f;
public override void Initialize()
{
@@ -168,17 +169,21 @@ namespace Content.Shared.Friction
private float GetTileFriction(PhysicsComponent body, TransformComponent xform)
{
// TODO: Make IsWeightless event-based; we already have grid traversals tracked so just raise events
if (_gravity.IsWeightless(body.Owner, body, xform) ||
!_mapManager.TryGetGrid(xform.GridUid, out var grid))
if (_gravity.IsWeightless(body.Owner, body, xform))
return 0.0f;
if (!xform.Coordinates.IsValid(EntityManager)) return 0.0f;
if (_mapManager.TryGetGrid(xform.GridUid, out var grid))
{
var tile = grid.GetTileRef(xform.Coordinates);
var tileDef = _tileDefinitionManager[tile.Tile.TypeId];
return tileDef.Friction;
}
return TryComp<TileFrictionModifierComponent>(xform.MapUid, out var friction) ? friction.Modifier : DefaultFriction;
}
[NetSerializable, Serializable]
private sealed class TileFrictionComponentState : ComponentState
{

View File

@@ -1,10 +1,8 @@
using Robust.Shared.Map;
namespace Content.Shared.Gravity
{
public sealed class GravityChangedMessage : EntityEventArgs
public sealed class GravityChangedEvent : EntityEventArgs
{
public GravityChangedMessage(EntityUid changedGridIndex, bool newGravityState)
public GravityChangedEvent(EntityUid changedGridIndex, bool newGravityState)
{
HasGravity = newGravityState;
ChangedGridIndex = changedGridIndex;

View File

@@ -12,48 +12,19 @@ namespace Content.Shared.Gravity
public SoundSpecifier GravityShakeSound { get; set; } = new SoundPathSpecifier("/Audio/Effects/alert.ogg");
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled
public bool EnabledVV
{
get => _enabled;
get => Enabled;
set
{
if (_enabled == value) return;
_enabled = value;
if (_enabled)
{
Logger.Info($"Enabled gravity for {Owner}");
}
else
{
Logger.Info($"Disabled gravity for {Owner}");
}
if (Enabled == value) return;
Enabled = value;
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, new GravityChangedEvent(Owner, value));
Dirty();
}
}
private bool _enabled;
public override ComponentState GetComponentState()
{
return new GravityComponentState(_enabled);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (curState is not GravityComponentState state) return;
Enabled = state.Enabled;
}
[Serializable, NetSerializable]
private sealed class GravityComponentState : ComponentState
{
public bool Enabled { get; }
public GravityComponentState(bool enabled)
{
Enabled = enabled;
}
}
[DataField("enabled")]
public bool Enabled;
}
}

View File

@@ -1,14 +1,17 @@
using Content.Shared.Alert;
using Content.Shared.Clothing;
using Content.Shared.Inventory;
using Content.Shared.Movement.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Serialization;
namespace Content.Shared.Gravity
{
public abstract class SharedGravitySystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, TransformComponent? xform = null)
@@ -21,23 +24,16 @@ namespace Content.Shared.Gravity
if (TryComp<MovementIgnoreGravityComponent>(uid, out var ignoreGravityComponent))
return ignoreGravityComponent.Weightless;
if (!Resolve(uid, ref xform)) return true;
bool gravityEnabled = false;
if (!Resolve(uid, ref xform))
return true;
// If grid / map has gravity
if ((TryComp<GravityComponent>(xform.GridUid, out var gravity) ||
TryComp(xform.MapUid, out gravity)) && gravity.Enabled)
{
gravityEnabled = gravity.Enabled;
if (gravityEnabled) return false;
return false;
}
// On the map then always weightless (unless it has gravity comp obv).
if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
return true;
// Something holding us down
if (_inventory.TryGetSlotEntity(uid, "shoes", out var ent))
{
@@ -45,21 +41,76 @@ namespace Content.Shared.Gravity
return false;
}
if (!gravityEnabled || !xform.Coordinates.IsValid(EntityManager)) return true;
var tile = grid.GetTileRef(xform.Coordinates).Tile;
return tile.IsEmpty;
return true;
}
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GridInitializeEvent>(HandleGridInitialize);
SubscribeLocalEvent<AlertsComponent, EntParentChangedMessage>(OnAlertsParentChange);
SubscribeLocalEvent<GravityChangedEvent>(OnGravityChange);
SubscribeLocalEvent<GravityComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<GravityComponent, ComponentHandleState>(OnHandleState);
}
private void OnHandleState(EntityUid uid, GravityComponent component, ref ComponentHandleState args)
{
if (args.Current is not GravityComponentState state) return;
if (component.EnabledVV == state.Enabled) return;
component.EnabledVV = state.Enabled;
RaiseLocalEvent(new GravityChangedEvent(uid, component.EnabledVV));
}
private void OnGetState(EntityUid uid, GravityComponent component, ref ComponentGetState args)
{
args.State = new GravityComponentState(component.EnabledVV);
}
private void OnGravityChange(GravityChangedEvent ev)
{
foreach (var (comp, xform) in EntityQuery<AlertsComponent, TransformComponent>(true))
{
if (xform.GridUid != ev.ChangedGridIndex) continue;
if (!ev.HasGravity)
{
_alerts.ShowAlert(comp.Owner, AlertType.Weightless);
}
else
{
_alerts.ClearAlert(comp.Owner, AlertType.Weightless);
}
}
}
private void OnAlertsParentChange(EntityUid uid, AlertsComponent component, ref EntParentChangedMessage args)
{
if (IsWeightless(component.Owner))
{
_alerts.ShowAlert(uid, AlertType.Weightless);
}
else
{
_alerts.ClearAlert(uid, AlertType.Weightless);
}
}
private void HandleGridInitialize(GridInitializeEvent ev)
{
EntityManager.EnsureComponent<GravityComponent>(ev.EntityUid);
}
[Serializable, NetSerializable]
private sealed class GravityComponentState : ComponentState
{
public bool Enabled { get; }
public GravityComponentState(bool enabled)
{
Enabled = enabled;
}
}
}
}

View File

@@ -1,17 +1,15 @@
using Content.Shared.Sound;
using Robust.Shared.GameStates;
namespace Content.Shared.Movement.Components
{
/// <summary>
/// Changes footstep sound
/// </summary>
[RegisterComponent]
[RegisterComponent, NetworkedComponent]
public sealed class FootstepModifierComponent : Component
{
[DataField("footstepSoundCollection", required: true)]
public SoundSpecifier SoundCollection = default!;
[DataField("variation")]
public float Variation = default;
public SoundSpecifier Sound = default!;
}
}

View File

@@ -49,7 +49,7 @@ namespace Content.Shared.Movement.Components
var gridId = transform.GridUid;
if ((entityManager.TryGetComponent<GravityComponent>(transform.GridUid, out var gravity) ||
entityManager.TryGetComponent(transform.MapUid, out gravity)) && gravity.Enabled)
entityManager.TryGetComponent(transform.MapUid, out gravity)) && gravity.EnabledVV)
return false;
if (gridId == null)
@@ -67,7 +67,7 @@ namespace Content.Shared.Movement.Components
return false;
}
if (!entityManager.GetComponent<GravityComponent>(grid.GridEntityId).Enabled)
if (!entityManager.GetComponent<GravityComponent>(grid.GridEntityId).EnabledVV)
{
return true;
}

View File

@@ -36,7 +36,7 @@ public abstract class SharedJetpackSystem : EntitySystem
SubscribeLocalEvent<JetpackUserComponent, ComponentGetState>(OnJetpackUserGetState);
SubscribeLocalEvent<JetpackUserComponent, ComponentHandleState>(OnJetpackUserHandleState);
SubscribeLocalEvent<GravityChangedMessage>(OnJetpackUserGravityChanged);
SubscribeLocalEvent<GravityChangedEvent>(OnJetpackUserGravityChanged);
}
private void OnJetpackCanWeightlessMove(EntityUid uid, JetpackComponent component, ref CanWeightlessMoveEvent args)
@@ -44,7 +44,7 @@ public abstract class SharedJetpackSystem : EntitySystem
args.CanMove = true;
}
private void OnJetpackUserGravityChanged(GravityChangedMessage ev)
private void OnJetpackUserGravityChanged(GravityChangedEvent ev)
{
var gridUid = ev.ChangedGridIndex;
var jetpackQuery = GetEntityQuery<JetpackComponent>();

View File

@@ -0,0 +1,35 @@
using Content.Shared.Movement.Components;
using Content.Shared.Sound;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Movement.Systems;
public abstract partial class SharedMoverController
{
private void InitializeFootsteps()
{
SubscribeLocalEvent<FootstepModifierComponent, ComponentGetState>(OnFootGetState);
SubscribeLocalEvent<FootstepModifierComponent, ComponentHandleState>(OnFootHandleState);
}
private void OnFootHandleState(EntityUid uid, FootstepModifierComponent component, ref ComponentHandleState args)
{
if (args.Current is not FootstepModifierComponentState state) return;
component.Sound = state.Sound;
}
private void OnFootGetState(EntityUid uid, FootstepModifierComponent component, ref ComponentGetState args)
{
args.State = new FootstepModifierComponentState()
{
Sound = component.Sound,
};
}
[Serializable, NetSerializable]
private sealed class FootstepModifierComponentState : ComponentState
{
public SoundSpecifier Sound = default!;
}
}

View File

@@ -10,6 +10,7 @@ using Content.Shared.MobState.EntitySystems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Pulling.Components;
using Content.Shared.Sound;
using Content.Shared.Tag;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
@@ -61,6 +62,7 @@ namespace Content.Shared.Movement.Systems
public override void Initialize()
{
base.Initialize();
InitializeFootsteps();
InitializeInput();
InitializeMob();
InitializePushing();
@@ -215,12 +217,13 @@ namespace Content.Shared.Movement.Systems
: worldTotal.ToWorldAngle();
rotateXform.DeferUpdates = false;
if (!weightless && TryComp<MobMoverComponent>(mover.Owner, out var mobMover) && TryGetSound(mover, mobMover, xform, out var variation, out var sound))
if (!weightless && TryComp<MobMoverComponent>(mover.Owner, out var mobMover) &&
TryGetSound(weightless, mover, mobMover, xform, out var sound))
{
var soundModifier = mover.Sprinting ? 1.0f : FootstepWalkingAddedVolumeMultiplier;
SoundSystem.Play(sound,
SoundSystem.Play(sound.GetSound(),
GetSoundPlayers(mover.Owner),
mover.Owner, AudioHelpers.WithVariation(variation).WithVolume(FootstepVolume * soundModifier));
mover.Owner, sound.Params.WithVolume(FootstepVolume * soundModifier));
}
}
@@ -313,19 +316,17 @@ namespace Content.Shared.Movement.Systems
protected abstract bool CanSound();
private bool TryGetSound(InputMoverComponent mover, MobMoverComponent mobMover, TransformComponent xform, out float variation, [NotNullWhen(true)] out string? sound)
private bool TryGetSound(bool weightless, InputMoverComponent mover, MobMoverComponent mobMover, TransformComponent xform, [NotNullWhen(true)] out SoundSpecifier? sound)
{
sound = null;
variation = 0f;
if (!CanSound() || !_tags.HasTag(mover.Owner, "FootstepSound")) return false;
var coordinates = xform.Coordinates;
var gridId = coordinates.GetGridUid(EntityManager);
var distanceNeeded = mover.Sprinting ? StepSoundMoveDistanceRunning : StepSoundMoveDistanceWalking;
// Handle footsteps.
if (_mapManager.GridExists(gridId))
if (!weightless)
{
// Can happen when teleporting between grids.
if (!coordinates.TryDistance(EntityManager, mobMover.LastPosition, out var distance) ||
@@ -344,7 +345,6 @@ namespace Content.Shared.Movement.Systems
return false;
}
DebugTools.Assert(gridId != null);
mobMover.LastPosition = coordinates;
if (mobMover.StepSoundDistance < distanceNeeded) return false;
@@ -354,19 +354,31 @@ namespace Content.Shared.Movement.Systems
if (_inventory.TryGetSlotEntity(mover.Owner, "shoes", out var shoes) &&
EntityManager.TryGetComponent<FootstepModifierComponent>(shoes, out var modifier))
{
sound = modifier.SoundCollection.GetSound();
variation = modifier.Variation;
sound = modifier.Sound;
return true;
}
return TryGetFootstepSound(gridId!.Value, coordinates, out variation, out sound);
return TryGetFootstepSound(coordinates, out sound);
}
private bool TryGetFootstepSound(EntityUid gridId, EntityCoordinates coordinates, out float variation, [NotNullWhen(true)] out string? sound)
private bool TryGetFootstepSound(EntityCoordinates coordinates, [NotNullWhen(true)] out SoundSpecifier? sound)
{
variation = 0f;
sound = null;
var grid = _mapManager.GetGrid(gridId);
var gridUid = coordinates.GetGridUid(EntityManager);
// Fallback to the map
if (gridUid == null)
{
if (TryComp<FootstepModifierComponent>(coordinates.GetMapUid(EntityManager), out var modifier))
{
sound = modifier.Sound;
return true;
}
return false;
}
var grid = _mapManager.GetGrid(gridUid.Value);
var tile = grid.GetTileRef(coordinates);
if (tile.IsSpace(_tileDefinitionManager)) return false;
@@ -377,18 +389,15 @@ namespace Content.Shared.Movement.Systems
{
if (EntityManager.TryGetComponent(maybeFootstep, out FootstepModifierComponent? footstep))
{
sound = footstep.SoundCollection.GetSound();
variation = footstep.Variation;
sound = footstep.Sound;
return true;
}
}
// Walking on a tile.
var def = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
sound = def.FootstepSounds?.GetSound();
variation = FootstepVariation;
return !string.IsNullOrEmpty(sound);
sound = def.FootstepSounds;
return sound != null;
}
}
}

View File

@@ -0,0 +1,28 @@
using JetBrains.Annotations;
using Robust.Shared.GameStates;
namespace Content.Shared.Parallax;
/// <summary>
/// Handles per-map parallax
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class ParallaxComponent : Component
{
// I wish I could use a typeserializer here but parallax is extremely client-dependent.
[DataField("parallax")]
public string Parallax = "Default";
[UsedImplicitly, ViewVariables(VVAccess.ReadWrite)]
// ReSharper disable once InconsistentNaming
public string ParallaxVV
{
get => Parallax;
set
{
if (value.Equals(Parallax)) return;
Parallax = value;
IoCManager.Resolve<IEntityManager>().Dirty(this);
}
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Parallax;
/// <summary>
/// Handles per-map parallax in sim. Out of sim parallax is handled by ParallaxManager.
/// </summary>
public abstract class SharedParallaxSystem: EntitySystem
{
[Serializable, NetSerializable]
protected sealed class ParallaxComponentState : ComponentState
{
public string Parallax = string.Empty;
}
}

View File

@@ -89,8 +89,6 @@ grids:
tiles: MAAAAC0AAAAtAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAALQAAAC0AAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAAAAAAAAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAALQAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: 1,-2
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAwAAAAMAAAADAAAAAwAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAtAAAAMAAAADAAAAAtAAAAMAAAADAAAAAtAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAALQAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAALQAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAMAAAADAAAAAwAAAALQAAADAAAAAwAAAAMAAAAA==
- ind: -2,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: -2,0
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAwAAAAMAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAAC0AAAAtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAwAAAAMAAAADAAAAAtAAAALQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAAC0AAAAtAAAALQAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAA==
- ind: -2,1
@@ -107,12 +105,6 @@ grids:
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: -3,-2
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtAAAALQAAAC0AAAAtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALQAAAC0AAAAtAAAALQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0AAAAtAAAALQAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtAAAALQAAAC0AAAAtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: -4,0
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: -4,1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: -4,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: 0,2
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
- ind: 1,2
@@ -221,8 +213,6 @@ entities:
- pos: 3.9415665,-8.34479
parent: 104
type: Transform
- canCollide: False
type: Physics
- solution: drink
type: DrainableSolution
- uid: 16
@@ -238,8 +228,6 @@ entities:
- pos: 3.5882664,-8.344303
parent: 104
type: Transform
- canCollide: False
type: Physics
- uid: 18
type: computerBodyScanner
components:
@@ -293,8 +281,6 @@ entities:
- pos: -0.5303154,-6.2851996
parent: 104
type: Transform
- canCollide: False
type: Physics
- solution: drink
type: DrainableSolution
- uid: 26
@@ -303,8 +289,6 @@ entities:
- pos: 4.4214306,-6.3946886
parent: 104
type: Transform
- canCollide: False
type: Physics
- uid: 27
type: Windoor
components:
@@ -792,7 +776,7 @@ entities:
- name: Syndicate Outpost
type: MetaData
- pos: -23.318382,8.069332
parent: null
parent: 1291
type: Transform
- whitelist:
tags:
@@ -800,8 +784,8 @@ entities:
type: FTLDestination
- index: 0
type: MapGrid
- angularDamping: 100
linearDamping: 50
- angularDamping: 0.05
linearDamping: 0.05
fixedRotation: False
bodyType: Dynamic
type: Physics
@@ -1119,47 +1103,47 @@ entities:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-13
171:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-15
170:
166:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-14
173:
167:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-17
172:
coordinates: -9,-15
168:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-16
174:
169:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-17
170:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-18
175:
171:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-19
176:
172:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-20
177:
173:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-21
178:
174:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-22
179:
175:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-23
180:
176:
color: '#FFFFFFFF'
id: WarnLineS
coordinates: -9,-24
@@ -1533,19 +1517,19 @@ entities:
color: '#FFFFFFFF'
id: Caution
coordinates: -18,1
164:
162:
color: '#FFFFFFFF'
id: WarningLine
coordinates: -18,1
167:
163:
color: '#FFFFFFFF'
id: Box
coordinates: -14,1
168:
164:
color: '#FFFFFFFF'
id: StandClear
coordinates: -14,1
169:
165:
color: '#FFFFFFFF'
id: WarnEnd
coordinates: -6,21
@@ -5703,6 +5687,46 @@ entities:
18,32: 0
19,32: 0
20,32: 0
-32,1: 1
-32,2: 1
-32,3: 1
-32,4: 1
-31,1: 1
-31,2: 1
-31,3: 1
-31,4: 1
-30,1: 1
-30,2: 1
-30,3: 1
-30,4: 1
-29,1: 1
-29,2: 1
-29,3: 1
-29,4: 1
-28,1: 1
-28,2: 1
-28,3: 1
-28,4: 1
-27,1: 1
-27,2: 1
-27,3: 1
-27,4: 1
-26,1: 1
-26,2: 1
-26,3: 1
-26,4: 1
-35,1: 1
-35,2: 1
-35,3: 1
-35,4: 1
-34,1: 1
-34,2: 1
-34,3: 1
-34,4: 1
-33,1: 1
-33,2: 1
-33,3: 1
-33,4: 1
uniqueMixes:
- volume: 2500
temperature: 293.15
@@ -5715,6 +5739,17 @@ entities:
- 0
- 0
- 0
- volume: 2500
temperature: 293.15
moles:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
type: GridAtmosphere
- uid: 105
type: WallRiveted
@@ -13554,6 +13589,32 @@ entities:
- pos: -10.5,-1.5
parent: 104
type: Transform
- uid: 1291
components:
- index: 2
type: Map
- footstepSoundCollection:
collection: footstep_snow
type: FootstepModifier
- gravityShakeSound: !type:SoundPathSpecifier
path: /Audio/Effects/alert.ogg
type: Gravity
- parallax: Sky
type: Parallax
- space: False
mixture:
volume: 2500
temperature: 248.15
moles:
- 21.824879
- 82.10312
- 0
- 0
- 0
- 0
- 0
- 0
type: MapAtmosphere
- uid: 1298
type: AsteroidRock
components:

View File

@@ -43,7 +43,6 @@
- type: Clothing
sprite: Clothing/Shoes/Misc/duck-slippers.rsi
- type: FootstepModifier
variation: 0.07
footstepSoundCollection:
collection: footstep_duck

View File

@@ -16,7 +16,6 @@
- type: EmitSoundOnUse
sound:
collection: BikeHorn
variation: 0.125
- type: UseDelay
delay: 0.5
- type: EmitSoundOnTrigger

View File

@@ -1,5 +1,5 @@
- type: parallax
id: default
id: Default
layers:
- texture:
!type:ImageParallaxTextureSource
@@ -33,3 +33,41 @@
configPath: "/Prototypes/Parallaxes/parallax_config.toml"
slowness: 0.875
layersLQUseHQ: false
# Because hyperspace and the menu need their own.
- type: parallax
id: FastSpace
layers:
- texture:
!type:ImageParallaxTextureSource
path: "/Textures/Parallaxes/layer1.png"
slowness: 0.5
scale: "1, 1"
- texture:
!type:GeneratedParallaxTextureSource
id: "hq_wizard_stars"
configPath: "/Prototypes/Parallaxes/parallax_config_stars.toml"
slowness: 0.25
- texture:
!type:GeneratedParallaxTextureSource
id: "hq_wizards_star_slower"
configPath: "/Prototypes/Parallaxes/parallax_config_stars-2.toml"
slowness: 0.15
- texture:
!type:GeneratedParallaxTextureSource
id: "hq_wizard_stars_dim"
configPath: "/Prototypes/Parallaxes/parallax_config_stars_dim.toml"
slowness: 0.375
- texture:
!type:GeneratedParallaxTextureSource
id: "hq_wizard_stars_dim_faster"
configPath: "/Prototypes/Parallaxes/parallax_config_stars_dim-2.toml"
slowness: 0.125
layersLQ:
- texture:
!type:GeneratedParallaxTextureSource
id: ""
configPath: "/Prototypes/Parallaxes/parallax_config.toml"
slowness: 0.5
layersLQUseHQ: false

View File

@@ -0,0 +1,24 @@
- type: parallax
id: Sky
layers:
- texture:
!type:ImageParallaxTextureSource
path: "/Textures/Parallaxes/land.png"
slowness: 0.98
scale: 1,1
- texture:
!type:ImageParallaxTextureSource
path: "/Textures/Parallaxes/noise.png"
slowness: 0.95
scale: "2, 2"
scrolling: "0.1, -0.05"
- type: parallax
id: Snow
layers:
- texture:
!type:ImageParallaxTextureSource
path: "/Textures/Tiles/snow.png"
slowness: 0
scale: "1, 1"
shader: ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 KiB

View File

@@ -0,0 +1,4 @@
land.png generated from https://cpetry.github.io/TextureGenerator-Online/
Licensed under MIT at https://github.com/cpetry/TextureGenerator-Online/blob/gh-pages/LICENSE
noise.png generated from https://giggster.com/guide/cloud-texture-generator/

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB