Gateway destinations (#21040)
* Gateway generation * Gateway stuff * gatewehs * mercenaries * play area * Range fixes and tweaks * weh * Gateway UI polish * Lots of fixes * Knock some items off * Fix dungeon spawning Realistically we should probably be using a salvage job. * wahwah * wehvs * expression * weh * eee * a * a * WEH * frfr * Gatwey * Fix gateway windows * Fix gateway windows * a * a * Better layer masking * a * a * Noise fixes * a * Fix fractal calculations * a * More fixes * Fixes * Add layers back in * Fixes * namespaces and ftl * Other TODO * Fix distance * Cleanup * Fix test
This commit is contained in:
@@ -17,7 +17,8 @@ public sealed class GatewayBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new GatewayWindow();
|
||||
_window = new GatewayWindow(EntMan.GetNetEntity(Owner));
|
||||
|
||||
_window.OpenPortal += destination =>
|
||||
{
|
||||
SendMessage(new GatewayOpenPortalMessage(destination));
|
||||
|
||||
@@ -4,10 +4,25 @@
|
||||
MinSize="800 360">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="NextCloseLabel"
|
||||
Text="{Loc 'gateway-window-portal-closing'}"
|
||||
Margin="5"></Label>
|
||||
<ProgressBar Name="NextCloseBar"
|
||||
<!-- This is wide as shit but makes it consistent with the cooldown label +
|
||||
handles localisations a bit better -->
|
||||
<Label Name="NextUnlockLabel"
|
||||
Text="{Loc 'gateway-window-portal-unlock'}"
|
||||
Margin="5"
|
||||
SetWidth="128"/>
|
||||
<ProgressBar Name="NextUnlockBar"
|
||||
HorizontalExpand="True"
|
||||
MinValue="0"
|
||||
MaxValue="1"
|
||||
SetHeight="25"/>
|
||||
<Label Name="NextUnlockText" Text="0" Margin="5"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="NextReadyLabel"
|
||||
Text="{Loc 'gateway-window-portal-cooldown'}"
|
||||
Margin="5"
|
||||
SetWidth="128"/>
|
||||
<ProgressBar Name="NextReadyBar"
|
||||
HorizontalExpand="True"
|
||||
MinValue="0"
|
||||
MaxValue="1"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Computer;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
@@ -19,30 +20,55 @@ public sealed partial class GatewayWindow : FancyWindow,
|
||||
private readonly IGameTiming _timing;
|
||||
|
||||
public event Action<NetEntity>? OpenPortal;
|
||||
private List<(NetEntity, string, TimeSpan, bool)> _destinations = default!;
|
||||
private NetEntity? _current;
|
||||
private TimeSpan _nextClose;
|
||||
private TimeSpan _lastOpen;
|
||||
private List<Label> _readyLabels = default!;
|
||||
private List<Button> _openButtons = default!;
|
||||
private List<GatewayDestinationData> _destinations = new();
|
||||
|
||||
public GatewayWindow()
|
||||
public readonly NetEntity Owner;
|
||||
|
||||
private NetEntity? _current;
|
||||
private TimeSpan _nextReady;
|
||||
private TimeSpan _cooldown;
|
||||
|
||||
private TimeSpan _unlockTime;
|
||||
private TimeSpan _nextUnlock;
|
||||
|
||||
/// <summary>
|
||||
/// Re-apply the state if the timer has elapsed.
|
||||
/// </summary>
|
||||
private GatewayBoundUserInterfaceState? _lastState;
|
||||
|
||||
/// <summary>
|
||||
/// Are we currently waiting on an unlock timer.
|
||||
/// </summary>
|
||||
private bool _isUnlockPending = true;
|
||||
|
||||
/// <summary>
|
||||
/// Are we currently waiting on a cooldown timer.
|
||||
/// </summary>
|
||||
private bool _isCooldownPending = true;
|
||||
|
||||
public GatewayWindow(NetEntity netEntity)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
var dependencies = IoCManager.Instance!;
|
||||
_timing = dependencies.Resolve<IGameTiming>();
|
||||
Owner = netEntity;
|
||||
|
||||
NextUnlockBar.ForegroundStyleBoxOverride = new StyleBoxFlat(Color.FromHex("#C74EBD"));
|
||||
}
|
||||
|
||||
public void UpdateState(GatewayBoundUserInterfaceState state)
|
||||
{
|
||||
_destinations = state.Destinations;
|
||||
_current = state.Current;
|
||||
_nextClose = state.NextClose;
|
||||
_lastOpen = state.LastOpen;
|
||||
_nextReady = state.NextReady;
|
||||
_cooldown = state.Cooldown;
|
||||
_unlockTime = state.UnlockTime;
|
||||
_nextUnlock = state.NextUnlock;
|
||||
|
||||
_isUnlockPending = _nextUnlock >= _timing.CurTime;
|
||||
_isCooldownPending = _nextReady >= _timing.CurTime;
|
||||
|
||||
Container.DisposeAllChildren();
|
||||
_readyLabels = new List<Label>(_destinations.Count);
|
||||
_openButtons = new List<Button>(_destinations.Count);
|
||||
|
||||
if (_destinations.Count == 0)
|
||||
{
|
||||
@@ -63,38 +89,63 @@ public sealed partial class GatewayWindow : FancyWindow,
|
||||
}
|
||||
|
||||
var now = _timing.CurTime;
|
||||
|
||||
foreach (var dest in _destinations)
|
||||
{
|
||||
var ent = dest.Item1;
|
||||
var name = dest.Item2;
|
||||
var nextReady = dest.Item3;
|
||||
var busy = dest.Item4;
|
||||
var ent = dest.Entity;
|
||||
var name = dest.Name;
|
||||
var locked = dest.Locked && _nextUnlock > _timing.CurTime;
|
||||
|
||||
var box = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Margin = new Thickness(5f, 5f)
|
||||
Margin = new Thickness(5f, 5f),
|
||||
};
|
||||
|
||||
box.AddChild(new Label()
|
||||
// HOW DO I ALIGN THESE GOODER
|
||||
var nameLabel = new RichTextLabel()
|
||||
{
|
||||
Text = name
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SetWidth = 156f,
|
||||
};
|
||||
|
||||
nameLabel.SetMessage(name);
|
||||
box.AddChild(nameLabel);
|
||||
// Buffer
|
||||
box.AddChild(new Control()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
|
||||
var readyLabel = new Label
|
||||
bool Pressable() => ent == _current || ent == Owner;
|
||||
|
||||
var buttonStripe = new StripeBack()
|
||||
{
|
||||
Text = ReadyText(now, nextReady, busy),
|
||||
Margin = new Thickness(10f, 0f, 0f, 0f)
|
||||
Visible = locked,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true,
|
||||
Margin = new Thickness(10f, 0f, 0f, 0f),
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("gateway-window-locked"),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
}
|
||||
}
|
||||
};
|
||||
_readyLabels.Add(readyLabel);
|
||||
box.AddChild(readyLabel);
|
||||
|
||||
var openButton = new Button()
|
||||
{
|
||||
Text = Loc.GetString("gateway-window-open-portal"),
|
||||
Pressed = ent == _current,
|
||||
Pressed = Pressable(),
|
||||
ToggleMode = true,
|
||||
Disabled = _current != null || busy || now < nextReady
|
||||
Disabled = now < _nextReady || Pressable(),
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
Margin = new Thickness(10f, 0f, 0f, 0f),
|
||||
Visible = !locked,
|
||||
SetHeight = 32f,
|
||||
};
|
||||
|
||||
openButton.OnPressed += args =>
|
||||
@@ -102,21 +153,22 @@ public sealed partial class GatewayWindow : FancyWindow,
|
||||
OpenPortal?.Invoke(ent);
|
||||
};
|
||||
|
||||
if (ent == _current)
|
||||
if (Pressable())
|
||||
{
|
||||
openButton.AddStyleClass(StyleBase.ButtonCaution);
|
||||
}
|
||||
|
||||
_openButtons.Add(openButton);
|
||||
box.AddChild(new BoxContainer()
|
||||
var buttonContainer = new BoxContainer()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Align = BoxContainer.AlignMode.End,
|
||||
Children =
|
||||
{
|
||||
openButton
|
||||
}
|
||||
});
|
||||
buttonStripe,
|
||||
openButton,
|
||||
},
|
||||
SetSize = new Vector2(128f, 40f),
|
||||
};
|
||||
|
||||
box.AddChild(buttonContainer);
|
||||
|
||||
Container.AddChild(new PanelContainer()
|
||||
{
|
||||
@@ -128,6 +180,8 @@ public sealed partial class GatewayWindow : FancyWindow,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_lastState = state;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -135,50 +189,66 @@ public sealed partial class GatewayWindow : FancyWindow,
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var now = _timing.CurTime;
|
||||
var dirtyState = false;
|
||||
|
||||
// if its not going to close then show it as empty
|
||||
if (_current == null)
|
||||
if (_nextUnlock == TimeSpan.Zero)
|
||||
{
|
||||
NextCloseBar.Value = 0f;
|
||||
NextCloseText.Text = "00:00";
|
||||
NextUnlockBar.Value = 1f;
|
||||
NextUnlockText.Text = "00:00";
|
||||
}
|
||||
else
|
||||
{
|
||||
var remaining = _nextClose - _timing.CurTime;
|
||||
var remaining = _nextUnlock - now;
|
||||
if (remaining < TimeSpan.Zero)
|
||||
{
|
||||
NextCloseBar.Value = 1f;
|
||||
if (_isUnlockPending)
|
||||
{
|
||||
dirtyState = true;
|
||||
_isUnlockPending = false;
|
||||
}
|
||||
|
||||
NextUnlockBar.Value = 1f;
|
||||
NextUnlockText.Text = "00:00";
|
||||
}
|
||||
else
|
||||
{
|
||||
NextUnlockBar.Value = 1f - (float) (remaining.TotalSeconds / _unlockTime.TotalSeconds);
|
||||
NextUnlockText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
|
||||
}
|
||||
}
|
||||
|
||||
// if its not going to close then show it as empty
|
||||
if (_current == null || _cooldown == TimeSpan.Zero)
|
||||
{
|
||||
NextReadyBar.Value = 1f;
|
||||
NextCloseText.Text = "00:00";
|
||||
}
|
||||
else
|
||||
{
|
||||
var openTime = _nextClose - _lastOpen;
|
||||
NextCloseBar.Value = 1f - (float) (remaining / openTime);
|
||||
var remaining = _nextReady - now;
|
||||
if (remaining < TimeSpan.Zero)
|
||||
{
|
||||
if (_isCooldownPending)
|
||||
{
|
||||
dirtyState = true;
|
||||
_isCooldownPending = false;
|
||||
}
|
||||
|
||||
NextReadyBar.Value = 1f;
|
||||
NextCloseText.Text = "00:00";
|
||||
}
|
||||
else
|
||||
{
|
||||
NextReadyBar.Value = 1f - (float) (remaining.TotalSeconds / _cooldown.TotalSeconds);
|
||||
NextCloseText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _destinations.Count; i++)
|
||||
if (dirtyState && _lastState != null)
|
||||
{
|
||||
var dest = _destinations[i];
|
||||
var nextReady = dest.Item3;
|
||||
var busy = dest.Item4;
|
||||
_readyLabels[i].Text = ReadyText(now, nextReady, busy);
|
||||
_openButtons[i].Disabled = _current != null || busy || now < nextReady;
|
||||
// Refresh UI buttons.
|
||||
UpdateState(_lastState);
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadyText(TimeSpan now, TimeSpan nextReady, bool busy)
|
||||
{
|
||||
if (busy)
|
||||
return Loc.GetString("gateway-window-already-active");
|
||||
|
||||
if (now < nextReady)
|
||||
{
|
||||
var time = nextReady - now;
|
||||
return Loc.GetString("gateway-window-ready-in", ("time", $"{time.Minutes:00}:{time.Seconds:00}"));
|
||||
}
|
||||
|
||||
return Loc.GetString("gateway-window-ready");
|
||||
}
|
||||
}
|
||||
|
||||
57
Content.Client/Overlays/StencilOverlay.RestrictedRange.cs
Normal file
57
Content.Client/Overlays/StencilOverlay.RestrictedRange.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Salvage;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class StencilOverlay
|
||||
{
|
||||
private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3 invMatrix)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var renderScale = args.Viewport.RenderScale.X;
|
||||
// TODO: This won't handle non-standard zooms so uhh yeah, not sure how to structure it on the shader side.
|
||||
var zoom = args.Viewport.Eye?.Zoom ?? Vector2.One;
|
||||
var length = zoom.X;
|
||||
var bufferRange = MathF.Min(10f, rangeComp.Range);
|
||||
|
||||
var pixelCenter = invMatrix.Transform(rangeComp.Origin);
|
||||
// Something something offset?
|
||||
var vertical = args.Viewport.Size.Y;
|
||||
|
||||
var pixelMaxRange = rangeComp.Range * renderScale / length * EyeManager.PixelsPerMeter;
|
||||
var pixelBufferRange = bufferRange * renderScale / length * EyeManager.PixelsPerMeter;
|
||||
var pixelMinRange = pixelMaxRange - pixelBufferRange;
|
||||
|
||||
_shader.SetParameter("position", new Vector2(pixelCenter.X, vertical - pixelCenter.Y));
|
||||
_shader.SetParameter("maxRange", pixelMaxRange);
|
||||
_shader.SetParameter("minRange", pixelMinRange);
|
||||
_shader.SetParameter("bufferRange", pixelBufferRange);
|
||||
_shader.SetParameter("gradient", 0.80f);
|
||||
|
||||
var worldAABB = args.WorldAABB;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
|
||||
var localAABB = invMatrix.TransformBox(worldAABB);
|
||||
|
||||
// Cut out the irrelevant bits via stencil
|
||||
// This is why we don't just use parallax; we might want specific tiles to get drawn over
|
||||
// particularly for planet maps or stations.
|
||||
worldHandle.RenderInRenderTarget(_blep!, () =>
|
||||
{
|
||||
worldHandle.UseShader(_shader);
|
||||
worldHandle.DrawRect(localAABB, Color.White);
|
||||
}, Color.Transparent);
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
|
||||
var curTime = _timing.RealTime;
|
||||
var sprite = _sprite.GetFrame(new SpriteSpecifier.Texture(new ResPath("/Textures/Parallaxes/noise.png")), curTime);
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, new Vector2(0.5f, 0f));
|
||||
}
|
||||
}
|
||||
68
Content.Client/Overlays/StencilOverlay.Weather.cs
Normal file
68
Content.Client/Overlays/StencilOverlay.Weather.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class StencilOverlay
|
||||
{
|
||||
private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var mapId = args.MapId;
|
||||
var worldAABB = args.WorldAABB;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
|
||||
|
||||
// Cut out the irrelevant bits via stencil
|
||||
// This is why we don't just use parallax; we might want specific tiles to get drawn over
|
||||
// particularly for planet maps or stations.
|
||||
worldHandle.RenderInRenderTarget(_blep!, () =>
|
||||
{
|
||||
var bodyQuery = _entManager.GetEntityQuery<PhysicsComponent>();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var weatherIgnoreQuery = _entManager.GetEntityQuery<IgnoreWeatherComponent>();
|
||||
|
||||
// idk if this is safe to cache in a field and clear sloth help
|
||||
var grids = new List<Entity<MapGridComponent>>();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref grids);
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
|
||||
Matrix3.Multiply(in matrix, in invMatrix, out var matty);
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
|
||||
{
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect(grid, tile, weatherIgnoreQuery, bodyQuery))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gridTile = new Box2(tile.GridIndices * grid.Comp.TileSize,
|
||||
(tile.GridIndices + Vector2i.One) * grid.Comp.TileSize);
|
||||
|
||||
worldHandle.DrawRect(gridTile, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
}, Color.Transparent);
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
|
||||
var curTime = _timing.RealTime;
|
||||
var sprite = _sprite.GetFrame(weatherProto.Sprite, curTime);
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha));
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
77
Content.Client/Overlays/StencilOverlay.cs
Normal file
77
Content.Client/Overlays/StencilOverlay.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Content.Client.Parallax;
|
||||
using Content.Client.Weather;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
/// <summary>
|
||||
/// Simple re-useable overlay with stencilled texture.
|
||||
/// </summary>
|
||||
public sealed partial class StencilOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
private readonly ParallaxSystem _parallax;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly WeatherSystem _weather;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
private IRenderTexture? _blep;
|
||||
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SpriteSystem sprite, WeatherSystem weather)
|
||||
{
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex + 1;
|
||||
_parallax = parallax;
|
||||
_transform = transform;
|
||||
_sprite = sprite;
|
||||
_weather = weather;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _protoManager.Index<ShaderPrototype>("WorldGradientCircle").InstanceUnique();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
|
||||
|
||||
if (_blep?.Texture.Size != args.Viewport.Size)
|
||||
{
|
||||
_blep?.Dispose();
|
||||
_blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "weather-stencil");
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<WeatherComponent>(mapUid, out var comp))
|
||||
{
|
||||
foreach (var (proto, weather) in comp.Weather)
|
||||
{
|
||||
if (!_protoManager.TryIndex<WeatherPrototype>(proto, out var weatherProto))
|
||||
continue;
|
||||
|
||||
var alpha = _weather.GetPercent(weather, mapUid);
|
||||
DrawWeather(args, weatherProto, alpha, invMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<RestrictedRangeComponent>(mapUid, out var restrictedRangeComponent))
|
||||
{
|
||||
DrawRestrictedRange(args, restrictedRangeComponent, invMatrix);
|
||||
}
|
||||
|
||||
args.WorldHandle.UseShader(null);
|
||||
args.WorldHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
}
|
||||
27
Content.Client/Overlays/StencilOverlaySystem.cs
Normal file
27
Content.Client/Overlays/StencilOverlaySystem.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Content.Client.Parallax;
|
||||
using Content.Client.Weather;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class StencilOverlaySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly ParallaxSystem _parallax = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
[Dependency] private readonly WeatherSystem _weather = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _sprite, _weather));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlay.RemoveOverlay<StencilOverlay>();
|
||||
}
|
||||
}
|
||||
88
Content.Client/Parallax/BiomeDebugOverlay.cs
Normal file
88
Content.Client/Parallax/BiomeDebugOverlay.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.Parallax;
|
||||
|
||||
public sealed class BiomeDebugOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
|
||||
private BiomeSystem _biomes;
|
||||
private SharedMapSystem _maps;
|
||||
|
||||
private Font _font;
|
||||
|
||||
public BiomeDebugOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_biomes = _entManager.System<BiomeSystem>();
|
||||
_maps = _entManager.System<SharedMapSystem>();
|
||||
|
||||
_font = new VectorFont(_cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 12);
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||
|
||||
return _entManager.HasComponent<BiomeComponent>(mapUid);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var mousePos = _eyeManager.ScreenToMap(mouseScreenPos);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace || mousePos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||
|
||||
if (!_entManager.TryGetComponent(mapUid, out BiomeComponent? biomeComp) || !_entManager.TryGetComponent(mapUid, out MapGridComponent? grid))
|
||||
return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var nodePos = _maps.WorldToTile(mapUid, grid, mousePos.Position);
|
||||
|
||||
if (_biomes.TryGetEntity(nodePos, biomeComp, grid, out var ent))
|
||||
{
|
||||
var text = $"Entity: {ent}";
|
||||
sb.AppendLine(text);
|
||||
}
|
||||
|
||||
if (_biomes.TryGetDecals(nodePos, biomeComp.Layers, biomeComp.Seed, grid, out var decals))
|
||||
{
|
||||
var text = $"Decals: {decals.Count}";
|
||||
sb.AppendLine(text);
|
||||
|
||||
foreach (var decal in decals)
|
||||
{
|
||||
var decalText = $"- {decal.ID}";
|
||||
sb.AppendLine(decalText);
|
||||
}
|
||||
}
|
||||
|
||||
if (_biomes.TryGetBiomeTile(nodePos, biomeComp.Layers, biomeComp.Seed, grid, out var tile))
|
||||
{
|
||||
var tileText = $"Tile: {_tileDefManager[tile.Value.TypeId].ID}";
|
||||
sb.AppendLine(tileText);
|
||||
}
|
||||
|
||||
args.ScreenHandle.DrawString(_font, mouseScreenPos.Position + new Vector2(0f, 32f), sb.ToString());
|
||||
}
|
||||
}
|
||||
22
Content.Client/Parallax/Commands/ShowBiomeCommand.cs
Normal file
22
Content.Client/Parallax/Commands/ShowBiomeCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Parallax.Commands;
|
||||
|
||||
public sealed class ShowBiomeCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
|
||||
|
||||
public override string Command => "showbiome";
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (_overlayMgr.HasOverlay<BiomeDebugOverlay>())
|
||||
{
|
||||
_overlayMgr.RemoveOverlay<BiomeDebugOverlay>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayMgr.AddOverlay(new BiomeDebugOverlay());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Parallax.Data;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared.Parallax;
|
||||
@@ -72,4 +73,56 @@ public sealed class ParallaxSystem : SharedParallaxSystem
|
||||
{
|
||||
return TryComp<ParallaxComponent>(mapUid, out var parallax) ? parallax.Parallax : Fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a texture as parallax in the specified world handle.
|
||||
/// </summary>
|
||||
/// <param name="worldHandle"></param>
|
||||
/// <param name="worldAABB">WorldAABB to use</param>
|
||||
/// <param name="sprite">Sprite to draw</param>
|
||||
/// <param name="curTime">Current time, unused if scrolling not set</param>
|
||||
/// <param name="position">Current position of the parallax</param>
|
||||
/// <param name="scrolling">How much to scroll the parallax texture per second</param>
|
||||
/// <param name="scale">Scale of the texture</param>
|
||||
/// <param name="slowness">How slow the parallax moves compared to position</param>
|
||||
/// <param name="modulate">Color modulation applied to drawing the texture</param>
|
||||
public void DrawParallax(
|
||||
DrawingHandleWorld worldHandle,
|
||||
Box2 worldAABB,
|
||||
Texture sprite,
|
||||
TimeSpan curTime,
|
||||
Vector2 position,
|
||||
Vector2 scrolling,
|
||||
float scale = 1f,
|
||||
float slowness = 0f,
|
||||
Color? modulate = null)
|
||||
{
|
||||
// Size of the texture in world units.
|
||||
var size = sprite.Size / (float) EyeManager.PixelsPerMeter * scale;
|
||||
var scrolled = scrolling * (float) curTime.TotalSeconds;
|
||||
|
||||
// Origin - start with the parallax shift itself.
|
||||
var originBL = position * slowness + scrolled;
|
||||
|
||||
// Centre the image.
|
||||
originBL -= size / 2;
|
||||
|
||||
// Remove offset so we can floor.
|
||||
var flooredBL = worldAABB.BottomLeft - originBL;
|
||||
|
||||
// Floor to background size.
|
||||
flooredBL = (flooredBL / size).Floored() * size;
|
||||
|
||||
// Re-offset.
|
||||
flooredBL += originBL;
|
||||
|
||||
for (var x = flooredBL.X; x < worldAABB.Right; x += size.X)
|
||||
{
|
||||
for (var y = flooredBL.Y; y < worldAABB.Top; y += size.Y)
|
||||
{
|
||||
var box = Box2.FromDimensions(new Vector2(x, y), size);
|
||||
worldHandle.DrawTextureRect(sprite, box, modulate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
Content.Client/Salvage/RestrictedRangeSystem.cs
Normal file
7
Content.Client/Salvage/RestrictedRangeSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Salvage;
|
||||
|
||||
namespace Content.Client.Salvage;
|
||||
|
||||
public sealed class RestrictedRangeSystem : SharedRestrictedRangeSystem
|
||||
{
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Parallax;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Weather;
|
||||
|
||||
public sealed class WeatherOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly WeatherSystem _weather;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
private IRenderTexture? _blep;
|
||||
|
||||
public WeatherOverlay(SharedTransformSystem transform, SpriteSystem sprite, WeatherSystem weather)
|
||||
{
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex + 1;
|
||||
_transform = transform;
|
||||
_weather = weather;
|
||||
_sprite = sprite;
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
if (!_entManager.TryGetComponent<WeatherComponent>(_mapManager.GetMapEntityId(args.MapId), out var weather) ||
|
||||
weather.Weather.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.BeforeDraw(in args);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||
|
||||
if (!_entManager.TryGetComponent<WeatherComponent>(mapUid, out var comp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (proto, weather) in comp.Weather)
|
||||
{
|
||||
if (!_protoManager.TryIndex<WeatherPrototype>(proto, out var weatherProto))
|
||||
continue;
|
||||
|
||||
var alpha = _weather.GetPercent(weather, mapUid);
|
||||
DrawWorld(args, weatherProto, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawWorld(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var mapId = args.MapId;
|
||||
var worldAABB = args.WorldAABB;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
|
||||
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
|
||||
|
||||
if (_blep?.Texture.Size != args.Viewport.Size)
|
||||
{
|
||||
_blep?.Dispose();
|
||||
_blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "weather-stencil");
|
||||
}
|
||||
|
||||
// Cut out the irrelevant bits via stencil
|
||||
// This is why we don't just use parallax; we might want specific tiles to get drawn over
|
||||
// particularly for planet maps or stations.
|
||||
worldHandle.RenderInRenderTarget(_blep, () =>
|
||||
{
|
||||
var bodyQuery = _entManager.GetEntityQuery<PhysicsComponent>();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var weatherIgnoreQuery = _entManager.GetEntityQuery<IgnoreWeatherComponent>();
|
||||
|
||||
// idk if this is safe to cache in a field and clear sloth help
|
||||
var grids = new List<Entity<MapGridComponent>>();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref grids);
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
|
||||
Matrix3.Multiply(in matrix, in invMatrix, out var matty);
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
|
||||
{
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect(grid, tile, weatherIgnoreQuery, bodyQuery))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gridTile = new Box2(tile.GridIndices * grid.Comp.TileSize,
|
||||
(tile.GridIndices + Vector2i.One) * grid.Comp.TileSize);
|
||||
|
||||
worldHandle.DrawRect(gridTile, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
}, Color.Transparent);
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
|
||||
worldHandle.DrawTextureRect(_blep.Texture, worldBounds);
|
||||
Texture? sprite = null;
|
||||
var curTime = _timing.RealTime;
|
||||
|
||||
switch (weatherProto.Sprite)
|
||||
{
|
||||
case SpriteSpecifier.Rsi rsi:
|
||||
var rsiActual = _cache.GetResource<RSIResource>(rsi.RsiPath).RSI;
|
||||
rsiActual.TryGetState(rsi.RsiState, out var state);
|
||||
var frames = state!.GetFrames(RsiDirection.South);
|
||||
var delays = state.GetDelays();
|
||||
var totalDelay = delays.Sum();
|
||||
var time = curTime.TotalSeconds % totalDelay;
|
||||
var delaySum = 0f;
|
||||
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
{
|
||||
var delay = delays[i];
|
||||
delaySum += delay;
|
||||
|
||||
if (time > delaySum)
|
||||
continue;
|
||||
|
||||
sprite = frames[i];
|
||||
break;
|
||||
}
|
||||
|
||||
sprite ??= _sprite.Frame0(weatherProto.Sprite);
|
||||
break;
|
||||
case SpriteSpecifier.Texture texture:
|
||||
sprite = texture.GetTexture(_cache);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
|
||||
// TODO: This is very similar to parallax but we need stencil support but we can probably combine these somehow
|
||||
// and not make it spaghetti, while getting the advantages of not-duped code?
|
||||
|
||||
|
||||
// Okay I have spent like 5 hours on this at this point and afaict you have one of the following comprises:
|
||||
// - No scrolling so the weather is always centered on the player
|
||||
// - Crappy looking rotation but strafing looks okay and scrolls
|
||||
// - Crappy looking strafing but rotation looks okay.
|
||||
// - No rotation
|
||||
// - Storing state across frames to do scrolling and just having it always do topdown.
|
||||
|
||||
// I have chosen no rotation.
|
||||
|
||||
const float scale = 1f;
|
||||
const float slowness = 0f;
|
||||
var scrolling = Vector2.Zero;
|
||||
|
||||
// Size of the texture in world units.
|
||||
var size = (sprite.Size / (float) EyeManager.PixelsPerMeter) * scale;
|
||||
var scrolled = scrolling * (float) curTime.TotalSeconds;
|
||||
|
||||
// Origin - start with the parallax shift itself.
|
||||
var originBL = position * slowness + scrolled;
|
||||
|
||||
// Centre the image.
|
||||
originBL -= size / 2;
|
||||
|
||||
// Remove offset so we can floor.
|
||||
var flooredBL = args.WorldAABB.BottomLeft - originBL;
|
||||
|
||||
// Floor to background size.
|
||||
flooredBL = (flooredBL / size).Floored() * size;
|
||||
|
||||
// Re-offset.
|
||||
flooredBL += originBL;
|
||||
|
||||
for (var x = flooredBL.X; x < args.WorldAABB.Right; x += size.X)
|
||||
{
|
||||
for (var y = flooredBL.Y; y < args.WorldAABB.Top; y += size.Y)
|
||||
{
|
||||
var box = Box2.FromDimensions(new Vector2(x, y), size);
|
||||
worldHandle.DrawTextureRect(sprite, box, (weatherProto.Color ?? Color.White).WithAlpha(alpha));
|
||||
}
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ namespace Content.Client.Weather;
|
||||
|
||||
public sealed class WeatherSystem : SharedWeatherSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
@@ -28,16 +27,9 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new WeatherOverlay(_transform, EntityManager.System<SpriteSystem>(), this));
|
||||
SubscribeLocalEvent<WeatherComponent, ComponentHandleState>(OnWeatherHandleState);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<WeatherOverlay>();
|
||||
}
|
||||
|
||||
protected override void Run(EntityUid uid, WeatherData weather, WeatherPrototype weatherProto, float frameTime)
|
||||
{
|
||||
base.Run(uid, weather, weatherProto, frameTime);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Gateway.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Gateway.Components;
|
||||
|
||||
@@ -10,6 +11,25 @@ namespace Content.Server.Gateway.Components;
|
||||
[RegisterComponent, Access(typeof(GatewaySystem))]
|
||||
public sealed partial class GatewayComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this destination is shown in the gateway ui.
|
||||
/// If you are making a gateway for an admeme set this once you are ready for players to select it.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Can the gateway be interacted with? If false then only settable via admins / mappers.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Interactable = true;
|
||||
|
||||
/// <summary>
|
||||
/// Name as it shows up on the ui of station gateways.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public FormattedMessage Name = new();
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when opening the portal.
|
||||
/// </summary>
|
||||
@@ -32,24 +52,14 @@ public sealed partial class GatewayComponent : Component
|
||||
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Every other gateway destination on the server.
|
||||
/// Cooldown between opening portal / closing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Added on startup and when a new destination portal is created.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public HashSet<EntityUid> Destinations = new();
|
||||
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the portal will be closed.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextClose;
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the portal was last opened.
|
||||
/// Only used for UI.
|
||||
/// The time at which the portal can next be opened.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan LastOpen;
|
||||
public TimeSpan NextReady;
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
using Content.Server.Gateway.Systems;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Gateway.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A gateway destination linked to by station gateway(s).
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(GatewaySystem))]
|
||||
public sealed partial class GatewayDestinationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this destination is shown in the gateway ui.
|
||||
/// If you are making a gateway for an admeme set this once you are ready for players to select it.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Enabled;
|
||||
|
||||
/// <summary>
|
||||
/// Name as it shows up on the ui of station gateways.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Name = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Time at which this destination is ready to be linked to.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextReady;
|
||||
|
||||
/// <summary>
|
||||
/// How long the portal will be open for after linking.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan OpenTime = TimeSpan.FromSeconds(600);
|
||||
|
||||
/// <summary>
|
||||
/// How long the destination is not ready for after the portal closes.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan Cooldown = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// If true, the portal can be closed by alt clicking it.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Closeable;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Gateway.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Generates gateway destinations at a regular interval.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class GatewayGeneratorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Prototype to spawn on the generated map if applicable.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? Proto = "Gateway";
|
||||
|
||||
/// <summary>
|
||||
/// Next time another seed unlocks.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextUnlock;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to unlock another destination once one is taken.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UnlockCooldown = TimeSpan.FromMinutes(45);
|
||||
|
||||
/// <summary>
|
||||
/// Maps we've generated.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<EntityUid> Generated = new();
|
||||
|
||||
[DataField]
|
||||
public int MobLayerCount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Mob layers to pick from.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<BiomeMarkerLayerPrototype>> MobLayers = new()
|
||||
{
|
||||
"Carps",
|
||||
"Xenos",
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public int LootLayerCount = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Loot layers to pick from.
|
||||
/// </summary>
|
||||
public List<ProtoId<BiomeMarkerLayerPrototype>> LootLayers = new()
|
||||
{
|
||||
"OreTin",
|
||||
"OreQuartz",
|
||||
"OreGold",
|
||||
"OreSilver",
|
||||
"OrePlasma",
|
||||
"OreUranium",
|
||||
"OreBananium",
|
||||
"OreArtifactFragment",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Content.Server.Gateway.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Destination created by <see cref="GatewayGeneratorComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class GatewayGeneratorDestinationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Generator that created this destination.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Is the map locked from being used still or unlocked.
|
||||
/// Used in conjunction with the attached generator's NextUnlock.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Locked = true;
|
||||
|
||||
[DataField]
|
||||
public bool Loaded;
|
||||
|
||||
/// <summary>
|
||||
/// Seed used for this destination.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Origin of the gateway.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Vector2i Origin;
|
||||
}
|
||||
|
||||
234
Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs
Normal file
234
Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Gateway.Components;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Salvage;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Gateway.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Generates gateway destinations regularly and indefinitely that can be chosen from.
|
||||
/// </summary>
|
||||
public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
[Dependency] private readonly BiomeSystem _biome = default!;
|
||||
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
[Dependency] private readonly GatewaySystem _gateway = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
private const string PlanetNames = "names_borer";
|
||||
|
||||
// TODO:
|
||||
// Fix shader some more
|
||||
// Show these in UI
|
||||
// Use regular mobs for thingo.
|
||||
|
||||
// Use salvage mission params
|
||||
// Add the funny song
|
||||
// Put salvage params in the UI
|
||||
|
||||
// Re-use salvage config stuff for the RNG
|
||||
// Have it in the UI like expeditions.
|
||||
|
||||
// Also add weather coz it's funny.
|
||||
|
||||
// Add songs (incl. the downloaded one) to the ambient music playlist for planet probably.
|
||||
// Copy most of salvage mission spawner
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GatewayGeneratorComponent, EntityUnpausedEvent>(OnGeneratorUnpaused);
|
||||
SubscribeLocalEvent<GatewayGeneratorComponent, MapInitEvent>(OnGeneratorMapInit);
|
||||
SubscribeLocalEvent<GatewayGeneratorComponent, ComponentShutdown>(OnGeneratorShutdown);
|
||||
SubscribeLocalEvent<GatewayGeneratorDestinationComponent, AttemptGatewayOpenEvent>(OnGeneratorAttemptOpen);
|
||||
SubscribeLocalEvent<GatewayGeneratorDestinationComponent, GatewayOpenEvent>(OnGeneratorOpen);
|
||||
}
|
||||
|
||||
private void OnGeneratorShutdown(EntityUid uid, GatewayGeneratorComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var genUid in component.Generated)
|
||||
{
|
||||
if (Deleted(genUid))
|
||||
continue;
|
||||
|
||||
QueueDel(genUid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGeneratorUnpaused(Entity<GatewayGeneratorComponent> ent, ref EntityUnpausedEvent args)
|
||||
{
|
||||
ent.Comp.NextUnlock += args.PausedTime;
|
||||
}
|
||||
|
||||
private void OnGeneratorMapInit(EntityUid uid, GatewayGeneratorComponent generator, MapInitEvent args)
|
||||
{
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
GenerateDestination(uid, generator);
|
||||
}
|
||||
|
||||
Dirty(uid, generator);
|
||||
}
|
||||
|
||||
private void GenerateDestination(EntityUid uid, GatewayGeneratorComponent? generator = null)
|
||||
{
|
||||
if (!Resolve(uid, ref generator))
|
||||
return;
|
||||
|
||||
var tileDef = _tileDefManager["FloorSteel"];
|
||||
const int MaxOffset = 256;
|
||||
var tiles = new List<(Vector2i Index, Tile Tile)>();
|
||||
var seed = _random.Next();
|
||||
var random = new Random(seed);
|
||||
var mapId = _mapManager.CreateMap();
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
var origin = new Vector2i(random.Next(-MaxOffset, MaxOffset), random.Next(-MaxOffset, MaxOffset));
|
||||
var restriction = AddComp<RestrictedRangeComponent>(mapUid);
|
||||
restriction.Origin = origin;
|
||||
_biome.EnsurePlanet(mapUid, _protoManager.Index<BiomeTemplatePrototype>("Continental"), seed);
|
||||
|
||||
var grid = Comp<MapGridComponent>(mapUid);
|
||||
|
||||
for (var x = -2; x <= 2; x++)
|
||||
{
|
||||
for (var y = -2; y <= 2; y++)
|
||||
{
|
||||
tiles.Add((new Vector2i(x, y) + origin, new Tile(tileDef.TileId, variant: random.NextByte(tileDef.Variants))));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear area nearby as a sort of landing pad.
|
||||
_maps.SetTiles(mapUid, grid, tiles);
|
||||
|
||||
var gatewayName = SharedSalvageSystem.GetFTLName(_protoManager.Index<DatasetPrototype>(PlanetNames), seed);
|
||||
|
||||
_metadata.SetEntityName(mapUid, gatewayName);
|
||||
var originCoords = new EntityCoordinates(mapUid, origin);
|
||||
|
||||
var genDest = AddComp<GatewayGeneratorDestinationComponent>(mapUid);
|
||||
genDest.Origin = origin;
|
||||
genDest.Seed = seed;
|
||||
genDest.Generator = uid;
|
||||
|
||||
// Enclose the area
|
||||
var boundaryUid = Spawn(null, originCoords);
|
||||
var boundaryPhysics = AddComp<PhysicsComponent>(boundaryUid);
|
||||
var cShape = new ChainShape();
|
||||
// Don't need it to be a perfect circle, just need it to be loosely accurate.
|
||||
cShape.CreateLoop(Vector2.Zero, restriction.Range + 1f, false, count: 4);
|
||||
_fixtures.TryCreateFixture(
|
||||
boundaryUid,
|
||||
cShape,
|
||||
"boundary",
|
||||
collisionLayer: (int) (CollisionGroup.HighImpassable | CollisionGroup.Impassable | CollisionGroup.LowImpassable),
|
||||
body: boundaryPhysics);
|
||||
_physics.WakeBody(boundaryUid, body: boundaryPhysics);
|
||||
AddComp<BoundaryComponent>(boundaryUid);
|
||||
|
||||
// Create the gateway.
|
||||
var gatewayUid = SpawnAtPosition(generator.Proto, originCoords);
|
||||
var gatewayComp = Comp<GatewayComponent>(gatewayUid);
|
||||
_gateway.SetDestinationName(gatewayUid, FormattedMessage.FromMarkup($"[color=#D381C996]{gatewayName}[/color]"), gatewayComp);
|
||||
_gateway.SetEnabled(gatewayUid, true, gatewayComp);
|
||||
generator.Generated.Add(mapUid);
|
||||
}
|
||||
|
||||
private void OnGeneratorAttemptOpen(Entity<GatewayGeneratorDestinationComponent> ent, ref AttemptGatewayOpenEvent args)
|
||||
{
|
||||
if (ent.Comp.Loaded || args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!TryComp(ent.Comp.Generator, out GatewayGeneratorComponent? generatorComp))
|
||||
return;
|
||||
|
||||
if (generatorComp.NextUnlock + _metadata.GetPauseTime(ent.Owner) <= _timing.CurTime)
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnGeneratorOpen(Entity<GatewayGeneratorDestinationComponent> ent, ref GatewayOpenEvent args)
|
||||
{
|
||||
if (ent.Comp.Loaded)
|
||||
return;
|
||||
|
||||
if (TryComp(ent.Comp.Generator, out GatewayGeneratorComponent? generatorComp))
|
||||
{
|
||||
generatorComp.NextUnlock = _timing.CurTime + generatorComp.UnlockCooldown;
|
||||
_gateway.UpdateAllGateways();
|
||||
// Generate another destination to keep them going.
|
||||
GenerateDestination(ent.Comp.Generator);
|
||||
}
|
||||
|
||||
if (!TryComp(args.MapUid, out MapGridComponent? grid))
|
||||
return;
|
||||
|
||||
ent.Comp.Locked = false;
|
||||
ent.Comp.Loaded = true;
|
||||
|
||||
// Do dungeon
|
||||
var seed = ent.Comp.Seed;
|
||||
var origin = ent.Comp.Origin;
|
||||
var random = new Random(seed);
|
||||
var dungeonDistance = random.Next(3, 6);
|
||||
var dungeonRotation = _dungeon.GetDungeonRotation(seed);
|
||||
var dungeonPosition = (origin + dungeonRotation.RotateVec(new Vector2i(0, dungeonDistance))).Floored();
|
||||
|
||||
_dungeon.GenerateDungeon(_protoManager.Index<DungeonConfigPrototype>("Experiment"), args.MapUid, grid, dungeonPosition, seed);
|
||||
|
||||
// TODO: Dungeon mobs + loot.
|
||||
|
||||
// Do markers on the map.
|
||||
if (TryComp(ent.Owner, out BiomeComponent? biomeComp) && generatorComp != null)
|
||||
{
|
||||
// - Loot
|
||||
var lootLayers = generatorComp.LootLayers.ToList();
|
||||
|
||||
for (var i = 0; i < generatorComp.LootLayerCount; i++)
|
||||
{
|
||||
var layerIdx = random.Next(lootLayers.Count);
|
||||
var layer = lootLayers[layerIdx];
|
||||
lootLayers.RemoveSwap(layerIdx);
|
||||
|
||||
_biome.AddMarkerLayer(biomeComp, layer.Id);
|
||||
}
|
||||
|
||||
// - Mobs
|
||||
var mobLayers = generatorComp.MobLayers.ToList();
|
||||
|
||||
for (var i = 0; i < generatorComp.MobLayerCount; i++)
|
||||
{
|
||||
var layerIdx = random.Next(mobLayers.Count);
|
||||
var layer = mobLayers[layerIdx];
|
||||
mobLayers.RemoveSwap(layerIdx);
|
||||
|
||||
_biome.AddMarkerLayer(biomeComp, layer.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Server.Gateway.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Gateway;
|
||||
using Content.Shared.Popups;
|
||||
@@ -6,9 +8,8 @@ using Content.Shared.Teleportation.Components;
|
||||
using Content.Shared.Teleportation.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Gateway.Systems;
|
||||
|
||||
@@ -19,6 +20,8 @@ public sealed class GatewaySystem : EntitySystem
|
||||
[Dependency] private readonly LinkedEntitySystem _linkedEntity = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly StationSystem _stations = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
@@ -26,62 +29,109 @@ public sealed class GatewaySystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GatewayComponent, EntityUnpausedEvent>(OnGatewayUnpaused);
|
||||
SubscribeLocalEvent<GatewayComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<GatewayComponent, ActivatableUIOpenAttemptEvent>(OnGatewayOpenAttempt);
|
||||
SubscribeLocalEvent<GatewayComponent, BoundUIOpenedEvent>(UpdateUserInterface);
|
||||
SubscribeLocalEvent<GatewayComponent, GatewayOpenPortalMessage>(OnOpenPortal);
|
||||
|
||||
SubscribeLocalEvent<GatewayDestinationComponent, ComponentStartup>(OnDestinationStartup);
|
||||
SubscribeLocalEvent<GatewayDestinationComponent, ComponentShutdown>(OnDestinationShutdown);
|
||||
SubscribeLocalEvent<GatewayDestinationComponent, GetVerbsEvent<AlternativeVerb>>(OnDestinationGetVerbs);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
public void SetEnabled(EntityUid uid, bool value, GatewayComponent? component = null)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
if (!Resolve(uid, ref component) || component.Enabled == value)
|
||||
return;
|
||||
|
||||
// close portals after theyve been open long enough
|
||||
var query = EntityQueryEnumerator<GatewayComponent, PortalComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var _))
|
||||
{
|
||||
if (_timing.CurTime < comp.NextClose)
|
||||
continue;
|
||||
|
||||
ClosePortal(uid, comp);
|
||||
component.Enabled = value;
|
||||
UpdateAllGateways();
|
||||
}
|
||||
|
||||
private void OnGatewayUnpaused(EntityUid uid, GatewayComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.NextReady += args.PausedTime;
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, GatewayComponent comp, ComponentStartup args)
|
||||
{
|
||||
// add existing destinations
|
||||
var query = EntityQueryEnumerator<GatewayDestinationComponent>();
|
||||
while (query.MoveNext(out var dest, out _))
|
||||
{
|
||||
comp.Destinations.Add(dest);
|
||||
}
|
||||
|
||||
// no need to update ui since its just been created, just do portal
|
||||
UpdateAppearance(uid);
|
||||
}
|
||||
|
||||
private void OnGatewayOpenAttempt(EntityUid uid, GatewayComponent component, ref ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (!component.Enabled || !component.Interactable)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void UpdateUserInterface<T>(EntityUid uid, GatewayComponent comp, T args)
|
||||
{
|
||||
UpdateUserInterface(uid, comp);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, GatewayComponent comp)
|
||||
public void UpdateAllGateways()
|
||||
{
|
||||
var destinations = new List<(NetEntity, string, TimeSpan, bool)>();
|
||||
foreach (var destUid in comp.Destinations)
|
||||
var query = AllEntityQuery<GatewayComponent, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
var dest = Comp<GatewayDestinationComponent>(destUid);
|
||||
if (!dest.Enabled)
|
||||
UpdateUserInterface(uid, comp, xform);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, GatewayComponent comp, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
var destinations = new List<GatewayDestinationData>();
|
||||
var query = AllEntityQuery<GatewayComponent, TransformComponent>();
|
||||
|
||||
var nextUnlock = TimeSpan.Zero;
|
||||
var unlockTime = TimeSpan.Zero;
|
||||
|
||||
// Next unlock is based off of:
|
||||
// - Our station's unlock timer (if we have a station)
|
||||
// - If our map is a generated destination then use the generator that made it
|
||||
|
||||
if (TryComp(_stations.GetOwningStation(uid), out GatewayGeneratorComponent? generatorComp) ||
|
||||
(TryComp(xform.MapUid, out GatewayGeneratorDestinationComponent? generatorDestination) &&
|
||||
TryComp(generatorDestination.Generator, out generatorComp)))
|
||||
{
|
||||
nextUnlock = generatorComp.NextUnlock;
|
||||
unlockTime = generatorComp.UnlockCooldown;
|
||||
}
|
||||
|
||||
while (query.MoveNext(out var destUid, out var dest, out var destXform))
|
||||
{
|
||||
if (!dest.Enabled || destUid == uid)
|
||||
continue;
|
||||
|
||||
destinations.Add((GetNetEntity(destUid), dest.Name, dest.NextReady, HasComp<PortalComponent>(destUid)));
|
||||
// Show destination if either no destination comp on the map or it's ours.
|
||||
TryComp<GatewayGeneratorDestinationComponent>(destXform.MapUid, out var gatewayDestination);
|
||||
|
||||
destinations.Add(new GatewayDestinationData()
|
||||
{
|
||||
Entity = GetNetEntity(destUid),
|
||||
// Fallback to grid's ID if applicable.
|
||||
Name = dest.Name.IsEmpty && destXform.GridUid != null ? FormattedMessage.FromUnformatted(MetaData(destXform.GridUid.Value).EntityName) : dest.Name ,
|
||||
Portal = HasComp<PortalComponent>(destUid),
|
||||
// If NextUnlock < CurTime it's unlocked, however
|
||||
// we'll always send the client if it's locked
|
||||
// It can just infer unlock times locally and not have to worry about it here.
|
||||
Locked = gatewayDestination != null && gatewayDestination.Locked
|
||||
});
|
||||
}
|
||||
|
||||
_linkedEntity.GetLink(uid, out var current);
|
||||
var state = new GatewayBoundUserInterfaceState(destinations, GetNetEntity(current), comp.NextClose, comp.LastOpen);
|
||||
|
||||
var state = new GatewayBoundUserInterfaceState(
|
||||
destinations,
|
||||
GetNetEntity(current),
|
||||
comp.NextReady,
|
||||
comp.Cooldown,
|
||||
nextUnlock,
|
||||
unlockTime
|
||||
);
|
||||
|
||||
_ui.TrySetUiState(uid, GatewayUiKey.Key, state);
|
||||
}
|
||||
|
||||
@@ -92,38 +142,58 @@ public sealed class GatewaySystem : EntitySystem
|
||||
|
||||
private void OnOpenPortal(EntityUid uid, GatewayComponent comp, GatewayOpenPortalMessage args)
|
||||
{
|
||||
if (args.Session.AttachedEntity == null)
|
||||
if (args.Session.AttachedEntity == null || GetNetEntity(uid) == args.Destination ||
|
||||
!comp.Enabled || !comp.Interactable)
|
||||
return;
|
||||
|
||||
// if the gateway has an access reader check it before allowing opening
|
||||
var user = args.Session.AttachedEntity.Value;
|
||||
if (CheckAccess(user, uid))
|
||||
if (CheckAccess(user, uid, comp))
|
||||
return;
|
||||
|
||||
// can't link if portal is already open on either side, the destination is invalid or on cooldown
|
||||
var desto = GetEntity(args.Destination);
|
||||
|
||||
if (HasComp<PortalComponent>(uid) ||
|
||||
HasComp<PortalComponent>(desto) ||
|
||||
!TryComp<GatewayDestinationComponent>(desto, out var dest) ||
|
||||
// If it's already open / not enabled / we're not ready DENY.
|
||||
if (!TryComp<GatewayComponent>(desto, out var dest) ||
|
||||
!dest.Enabled ||
|
||||
_timing.CurTime < dest.NextReady)
|
||||
_timing.CurTime < _metadata.GetPauseTime(uid) + comp.NextReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: admin log???
|
||||
ClosePortal(uid, comp, false);
|
||||
OpenPortal(uid, comp, desto, dest);
|
||||
}
|
||||
|
||||
private void OpenPortal(EntityUid uid, GatewayComponent comp, EntityUid dest, GatewayDestinationComponent destComp)
|
||||
private void OpenPortal(EntityUid uid, GatewayComponent comp, EntityUid dest, GatewayComponent destComp, TransformComponent? destXform = null)
|
||||
{
|
||||
_linkedEntity.TryLink(uid, dest);
|
||||
EnsureComp<PortalComponent>(uid).CanTeleportToOtherMaps = true;
|
||||
EnsureComp<PortalComponent>(dest).CanTeleportToOtherMaps = true;
|
||||
if (!Resolve(dest, ref destXform) || destXform.MapUid == null)
|
||||
return;
|
||||
|
||||
var ev = new AttemptGatewayOpenEvent(destXform.MapUid.Value, dest);
|
||||
RaiseLocalEvent(destXform.MapUid.Value, ref ev);
|
||||
|
||||
if (ev.Cancelled)
|
||||
return;
|
||||
|
||||
_linkedEntity.OneWayLink(uid, dest);
|
||||
|
||||
var sourcePortal = EnsureComp<PortalComponent>(uid);
|
||||
var targetPortal = EnsureComp<PortalComponent>(dest);
|
||||
|
||||
sourcePortal.CanTeleportToOtherMaps = true;
|
||||
targetPortal.CanTeleportToOtherMaps = true;
|
||||
|
||||
sourcePortal.RandomTeleport = false;
|
||||
targetPortal.RandomTeleport = false;
|
||||
|
||||
var openEv = new GatewayOpenEvent(destXform.MapUid.Value, dest);
|
||||
RaiseLocalEvent(destXform.MapUid.Value, ref openEv);
|
||||
|
||||
// for ui
|
||||
comp.LastOpen = _timing.CurTime;
|
||||
// close automatically after time is up
|
||||
comp.NextClose = comp.LastOpen + destComp.OpenTime;
|
||||
comp.NextReady = _timing.CurTime + comp.Cooldown;
|
||||
|
||||
_audio.PlayPvs(comp.OpenSound, uid);
|
||||
_audio.PlayPvs(comp.OpenSound, dest);
|
||||
@@ -133,7 +203,7 @@ public sealed class GatewaySystem : EntitySystem
|
||||
UpdateAppearance(dest);
|
||||
}
|
||||
|
||||
private void ClosePortal(EntityUid uid, GatewayComponent? comp = null)
|
||||
private void ClosePortal(EntityUid uid, GatewayComponent? comp = null, bool update = true)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
@@ -142,7 +212,7 @@ public sealed class GatewaySystem : EntitySystem
|
||||
if (!_linkedEntity.GetLink(uid, out var dest))
|
||||
return;
|
||||
|
||||
if (TryComp<GatewayDestinationComponent>(dest, out var destComp))
|
||||
if (TryComp<GatewayComponent>(dest, out var destComp))
|
||||
{
|
||||
// portals closed, put it on cooldown and let it eventually be opened again
|
||||
destComp.NextReady = _timing.CurTime + destComp.Cooldown;
|
||||
@@ -153,46 +223,35 @@ public sealed class GatewaySystem : EntitySystem
|
||||
|
||||
_linkedEntity.TryUnlink(uid, dest.Value);
|
||||
RemComp<PortalComponent>(dest.Value);
|
||||
|
||||
if (update)
|
||||
{
|
||||
UpdateUserInterface(uid, comp);
|
||||
UpdateAppearance(uid);
|
||||
UpdateAppearance(dest.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestinationStartup(EntityUid uid, GatewayDestinationComponent comp, ComponentStartup args)
|
||||
private void OnDestinationStartup(EntityUid uid, GatewayComponent comp, ComponentStartup args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<GatewayComponent>();
|
||||
var query = AllEntityQuery<GatewayComponent>();
|
||||
while (query.MoveNext(out var gatewayUid, out var gateway))
|
||||
{
|
||||
gateway.Destinations.Add(uid);
|
||||
UpdateUserInterface(gatewayUid, gateway);
|
||||
}
|
||||
|
||||
UpdateAppearance(uid);
|
||||
}
|
||||
|
||||
private void OnDestinationShutdown(EntityUid uid, GatewayDestinationComponent comp, ComponentShutdown args)
|
||||
private void OnDestinationShutdown(EntityUid uid, GatewayComponent comp, ComponentShutdown args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<GatewayComponent>();
|
||||
var query = AllEntityQuery<GatewayComponent>();
|
||||
while (query.MoveNext(out var gatewayUid, out var gateway))
|
||||
{
|
||||
gateway.Destinations.Remove(uid);
|
||||
UpdateUserInterface(gatewayUid, gateway);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestinationGetVerbs(EntityUid uid, GatewayDestinationComponent comp, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!comp.Closeable || !args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
// a portal is open so add verb to close it
|
||||
args.Verbs.Add(new AlternativeVerb()
|
||||
{
|
||||
Act = () => TryClose(uid, args.User),
|
||||
Text = Loc.GetString("gateway-close-portal")
|
||||
});
|
||||
}
|
||||
|
||||
private void TryClose(EntityUid uid, EntityUid user)
|
||||
{
|
||||
// portal already closed so cant close it
|
||||
@@ -222,4 +281,31 @@ public sealed class GatewaySystem : EntitySystem
|
||||
_audio.PlayPvs(comp.AccessDeniedSound, uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetDestinationName(EntityUid gatewayUid, FormattedMessage gatewayName, GatewayComponent? gatewayComp = null)
|
||||
{
|
||||
if (!Resolve(gatewayUid, ref gatewayComp))
|
||||
return;
|
||||
|
||||
gatewayComp.Name = gatewayName;
|
||||
Dirty(gatewayUid, gatewayComp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on the target map when a GatewayDestination is attempted to be opened.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct AttemptGatewayOpenEvent(EntityUid MapUid, EntityUid GatewayDestinationUid)
|
||||
{
|
||||
public readonly EntityUid MapUid = MapUid;
|
||||
public readonly EntityUid GatewayDestinationUid = GatewayDestinationUid;
|
||||
|
||||
public bool Cancelled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised directed on the target map when a gateway is opened.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct GatewayOpenEvent(EntityUid MapUid, EntityUid GatewayDestinationUid);
|
||||
|
||||
@@ -27,9 +27,8 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public string Command => $"planet";
|
||||
public string 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)
|
||||
@@ -60,46 +59,10 @@ public sealed class PlanetCommand : IConsoleCommand
|
||||
return;
|
||||
}
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
MetaDataComponent? metadata = null;
|
||||
|
||||
var biome = _entManager.EnsureComponent<BiomeComponent>(mapUid);
|
||||
var biomeSystem = _entManager.System<BiomeSystem>();
|
||||
biomeSystem.SetSeed(biome, _random.Next());
|
||||
biomeSystem.SetTemplate(biome, biomeTemplate);
|
||||
_entManager.Dirty(biome);
|
||||
var mapUid = _mapManager.GetMapEntityId(mapId);
|
||||
biomeSystem.EnsurePlanet(mapUid, biomeTemplate);
|
||||
|
||||
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
|
||||
gravity.Enabled = true;
|
||||
gravity.Inherent = true;
|
||||
_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);
|
||||
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
|
||||
var mixture = new GasMixture(2500)
|
||||
{
|
||||
Temperature = 293.15f,
|
||||
Moles = moles,
|
||||
};
|
||||
|
||||
_entManager.System<AtmosphereSystem>().SetMapAtmosphere(mapUid, false, mixture, atmos);
|
||||
|
||||
_entManager.EnsureComponent<MapGridComponent>(mapUid);
|
||||
shell.WriteLine(Loc.GetString("cmd-planet-success", ("mapId", mapId)));
|
||||
}
|
||||
|
||||
|
||||
32
Content.Server/Movement/Systems/BoundarySystem.cs
Normal file
32
Content.Server/Movement/Systems/BoundarySystem.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Content.Shared.Movement.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Server.Movement.Systems;
|
||||
|
||||
public sealed class BoundarySystem : EntitySystem
|
||||
{
|
||||
/*
|
||||
* The real reason this even exists is because with out mover controller it's really easy to clip out of bounds on chain shapes.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BoundaryComponent, StartCollideEvent>(OnBoundaryCollide);
|
||||
}
|
||||
|
||||
private void OnBoundaryCollide(Entity<BoundaryComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
var center = _xform.GetWorldPosition(ent.Owner);
|
||||
var otherXform = Transform(args.OtherEntity);
|
||||
var collisionPoint = _xform.GetWorldPosition(otherXform);
|
||||
var offset = collisionPoint - center;
|
||||
offset = offset.Normalized() * (offset.Length() - ent.Comp.Offset);
|
||||
// If for whatever reason you want to yeet them to the other side.
|
||||
// offset = new Angle(MathF.PI).RotateVec(offset);
|
||||
|
||||
_xform.SetWorldPosition(otherXform, center + offset);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ public sealed partial class BiomeSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_proto.TryIndex<BiomeTemplatePrototype>(args[1], out var template))
|
||||
if (!ProtoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var template))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public sealed partial class BiomeSystem
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIDs<BiomeTemplatePrototype>(proto: _proto), "Biome template");
|
||||
CompletionHelper.PrototypeIDs<BiomeTemplatePrototype>(proto: ProtoManager), "Biome template");
|
||||
}
|
||||
|
||||
if (args.Length == 3)
|
||||
@@ -146,7 +146,7 @@ public sealed partial class BiomeSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_proto.HasIndex<BiomeMarkerLayerPrototype>(args[1]))
|
||||
if (!ProtoManager.HasIndex<BiomeMarkerLayerPrototype>(args[1]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -164,7 +164,7 @@ public sealed partial class BiomeSystem
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIDs<BiomeMarkerLayerPrototype>(proto: _proto), "Marker");
|
||||
CompletionHelper.PrototypeIDs<BiomeMarkerLayerPrototype>(proto: ProtoManager), "Marker");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Decals;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
@@ -32,10 +37,11 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly DecalSystem _decals = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _handledEntities = new();
|
||||
@@ -61,20 +67,20 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BiomeComponent, ComponentStartup>(OnBiomeStartup);
|
||||
Log.Level = LogLevel.Debug;
|
||||
SubscribeLocalEvent<BiomeComponent, MapInitEvent>(OnBiomeMapInit);
|
||||
SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
|
||||
SubscribeLocalEvent<ShuttleFlattenEvent>(OnShuttleFlatten);
|
||||
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, SetLoadRange, true);
|
||||
InitializeCommands();
|
||||
_proto.PrototypesReloaded += ProtoReload;
|
||||
ProtoManager.PrototypesReloaded += ProtoReload;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, SetLoadRange);
|
||||
_proto.PrototypesReloaded -= ProtoReload;
|
||||
ProtoManager.PrototypesReloaded -= ProtoReload;
|
||||
}
|
||||
|
||||
private void ProtoReload(PrototypesReloadedEventArgs obj)
|
||||
@@ -100,11 +106,6 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
_loadArea = new Box2(-_loadRange, -_loadRange, _loadRange, _loadRange);
|
||||
}
|
||||
|
||||
private void OnBiomeStartup(EntityUid uid, BiomeComponent component, ComponentStartup args)
|
||||
{
|
||||
component.Noise.SetSeed(component.Seed);
|
||||
}
|
||||
|
||||
private void OnBiomeMapInit(EntityUid uid, BiomeComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.Seed != -1)
|
||||
@@ -116,7 +117,6 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
public void SetSeed(BiomeComponent component, int seed)
|
||||
{
|
||||
component.Seed = seed;
|
||||
component.Noise.SetSeed(seed);
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
public void AddMarkerLayer(BiomeComponent component, string marker)
|
||||
{
|
||||
if (!_proto.HasIndex<BiomeMarkerLayerPrototype>(marker))
|
||||
if (!ProtoManager.HasIndex<BiomeMarkerLayerPrototype>(marker))
|
||||
{
|
||||
// TODO: Log when we get a sawmill
|
||||
return;
|
||||
@@ -234,7 +234,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
var mod = biome.ModifiedTiles.GetOrNew(chunk * ChunkSize);
|
||||
|
||||
if (!mod.Add(index) || !TryGetBiomeTile(index, biome.Layers, biome.Noise, grid, out var tile))
|
||||
if (!mod.Add(index) || !TryGetBiomeTile(index, biome.Layers, biome.Seed, grid, out var tile))
|
||||
continue;
|
||||
|
||||
// If we flag it as modified then the tile is never set so need to do it ourselves.
|
||||
@@ -243,7 +243,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
_mapSystem.SetTiles(ev.MapUid, grid, tiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -256,7 +256,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
foreach (var layer in markers)
|
||||
{
|
||||
var proto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var proto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var enumerator = new ChunkIndicesEnumerator(area, proto.Size);
|
||||
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
@@ -294,7 +294,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
foreach (var layer in biome.MarkerLayers)
|
||||
{
|
||||
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
AddMarkerChunksInRange(biome, worldPos, layerProto);
|
||||
}
|
||||
}
|
||||
@@ -313,7 +313,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
foreach (var layer in biome.MarkerLayers)
|
||||
{
|
||||
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
AddMarkerChunksInRange(biome, worldPos, layerProto);
|
||||
}
|
||||
}
|
||||
@@ -323,12 +323,10 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
while (loadBiomes.MoveNext(out var gridUid, out var biome, out var grid))
|
||||
{
|
||||
var noise = biome.Noise;
|
||||
|
||||
// Load new chunks
|
||||
LoadChunks(biome, gridUid, grid, noise, xformQuery);
|
||||
LoadChunks(biome, gridUid, grid, biome.Seed, xformQuery);
|
||||
// Unload old chunks
|
||||
UnloadChunks(biome, gridUid, grid, noise);
|
||||
UnloadChunks(biome, gridUid, grid, biome.Seed);
|
||||
}
|
||||
|
||||
_handledEntities.Clear();
|
||||
@@ -376,36 +374,42 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
BiomeComponent component,
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
FastNoiseLite noise,
|
||||
int seed,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var markers = _markerChunks[component];
|
||||
var loadedMarkers = component.LoadedMarkers;
|
||||
var idx = 0;
|
||||
|
||||
foreach (var (layer, chunks) in markers)
|
||||
{
|
||||
// I know dictionary ordering isn't guaranteed but I just need something to differentiate seeds.
|
||||
idx++;
|
||||
var localIdx = idx;
|
||||
|
||||
Parallel.ForEach(chunks, new ParallelOptions() { MaxDegreeOfParallelism = _parallel.ParallelProcessCount }, chunk =>
|
||||
{
|
||||
if (loadedMarkers.TryGetValue(layer, out var mobChunks) && mobChunks.Contains(chunk))
|
||||
return;
|
||||
|
||||
var noiseCopy = new FastNoiseLite();
|
||||
_serManager.CopyTo(component.Noise, ref noiseCopy, notNullableOverride: true);
|
||||
// Get the set of spawned nodes to avoid overlap.
|
||||
var spawnSet = _tilePool.Get();
|
||||
var frontier = new ValueList<Vector2i>(32);
|
||||
|
||||
// Make a temporary version and copy back in later.
|
||||
var pending = new Dictionary<Vector2i, Dictionary<string, List<Vector2i>>>();
|
||||
|
||||
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var buffer = layerProto.Radius / 2f;
|
||||
var rand = new Random(noise.GetSeed() + chunk.X * 8 + chunk.Y + layerProto.GetHashCode());
|
||||
var markerSeed = seed + chunk.X * ChunkSize + chunk.Y + localIdx;
|
||||
var rand = new Random(markerSeed);
|
||||
|
||||
// We treat a null entity mask as requiring nothing else on the tile
|
||||
var lower = (int) Math.Floor(buffer);
|
||||
var upper = (int) Math.Ceiling(layerProto.Size - buffer);
|
||||
|
||||
// TODO: Need poisson but crashes whenever I use moony's due to inputs or smth idk
|
||||
// Get the total amount of groups to spawn across the entire chunk.
|
||||
var count = (int) ((layerProto.Size - buffer) * (layerProto.Size - buffer) /
|
||||
(layerProto.Radius * layerProto.Radius));
|
||||
count = Math.Min(count, layerProto.MaxCount);
|
||||
@@ -414,14 +418,14 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
// It will bias edge tiles significantly more but will make the CPU cry less.
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var groupCount = layerProto.GroupCount;
|
||||
var groupSize = rand.Next(layerProto.MinGroupSize, layerProto.MaxGroupSize + 1);
|
||||
var startNodeX = rand.Next(lower, upper + 1);
|
||||
var startNodeY = rand.Next(lower, upper + 1);
|
||||
var startNode = new Vector2i(startNodeX, startNodeY);
|
||||
frontier.Clear();
|
||||
frontier.Add(startNode + chunk);
|
||||
|
||||
while (groupCount > 0 && frontier.Count > 0)
|
||||
while (groupSize >= 0 && frontier.Count > 0)
|
||||
{
|
||||
var frontierIndex = rand.Next(frontier.Count);
|
||||
var node = frontier[frontierIndex];
|
||||
@@ -435,7 +439,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
if (x != 0 && y != 0)
|
||||
continue;
|
||||
|
||||
var neighbor = new Vector2i(x + node.X, y + node.Y);
|
||||
var neighbor = new Vector2i(node.X + x, node.Y + y);
|
||||
var chunkOffset = neighbor - chunk;
|
||||
|
||||
// Check if it's inbounds.
|
||||
@@ -455,19 +459,29 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
|
||||
// Check if it's a valid spawn, if so then use it.
|
||||
var enumerator = grid.GetAnchoredEntitiesEnumerator(node);
|
||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, node);
|
||||
|
||||
if (enumerator.MoveNext(out _))
|
||||
continue;
|
||||
|
||||
// Check if mask matches.
|
||||
TryGetEntity(node, component.Layers, noiseCopy, grid, out var proto);
|
||||
// Check if mask matches // anything blocking.
|
||||
TryGetEntity(node, component, grid, out var proto);
|
||||
|
||||
if (proto != layerProto.EntityMask)
|
||||
// If there's an existing entity and it doesn't match the mask then skip.
|
||||
if (layerProto.EntityMask.Count > 0 &&
|
||||
(proto == null ||
|
||||
!layerProto.EntityMask.ContainsKey(proto)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's just a flat spawn then just check for anything blocking.
|
||||
if (proto != null && layerProto.Prototype != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(layerProto.EntityMask.Count == 0 || !string.IsNullOrEmpty(proto));
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(node, ChunkSize) * ChunkSize;
|
||||
|
||||
if (!pending.TryGetValue(chunkOrigin, out var pendingMarkers))
|
||||
@@ -482,9 +496,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
pendingMarkers[layer] = layerMarkers;
|
||||
}
|
||||
|
||||
// Log.Info($"Added node at {actualNode} for chunk {chunkOrigin}");
|
||||
layerMarkers.Add(node);
|
||||
groupCount--;
|
||||
groupSize--;
|
||||
spawnSet.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,7 +541,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
|
||||
// Load NOW!
|
||||
LoadChunk(component, gridUid, grid, chunk, noise, tiles, xformQuery);
|
||||
LoadChunk(component, gridUid, grid, chunk, seed, tiles, xformQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,7 +553,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
Vector2i chunk,
|
||||
FastNoiseLite noise,
|
||||
int seed,
|
||||
List<(Vector2i, Tile)> tiles,
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
@@ -551,7 +565,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
{
|
||||
foreach (var (layer, nodes) in layers)
|
||||
{
|
||||
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
@@ -559,15 +573,27 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
|
||||
// Need to ensure the tile under it has loaded for anchoring.
|
||||
if (TryGetBiomeTile(node, component.Layers, component.Noise, grid, out var tile))
|
||||
if (TryGetBiomeTile(node, component.Layers, seed, grid, out var tile))
|
||||
{
|
||||
grid.SetTile(node, tile.Value);
|
||||
_mapSystem.SetTile(gridUid, grid, node, tile.Value);
|
||||
}
|
||||
|
||||
string? prototype;
|
||||
|
||||
if (TryGetEntity(node, component, grid, out var proto) &&
|
||||
layerProto.EntityMask.TryGetValue(proto, out var maskedProto))
|
||||
{
|
||||
prototype = maskedProto;
|
||||
}
|
||||
else
|
||||
{
|
||||
prototype = layerProto.Prototype;
|
||||
}
|
||||
|
||||
// If it is a ghost role then purge it
|
||||
// TODO: This is *kind* of a bandaid but natural mobs spawns needs a lot more work.
|
||||
// Ideally we'd just have ghost role and non-ghost role variants for some stuff.
|
||||
var uid = EntityManager.CreateEntityUninitialized(layerProto.Prototype, grid.GridTileToLocal(node));
|
||||
var uid = EntityManager.CreateEntityUninitialized(prototype, _mapSystem.GridTileToLocal(gridUid, grid, node));
|
||||
RemComp<GhostTakeoverAvailableComponent>(uid);
|
||||
RemComp<GhostRoleComponent>(uid);
|
||||
EntityManager.InitializeAndStartEntity(uid);
|
||||
@@ -585,22 +611,22 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
{
|
||||
var indices = new Vector2i(x + chunk.X, y + chunk.Y);
|
||||
|
||||
// Pass in null so we don't try to get the tileref.
|
||||
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)
|
||||
if (_mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
// Pass in null so we don't try to get the tileref.
|
||||
if (!TryGetBiomeTile(indices, component.Layers, noise, null, out var biomeTile) || biomeTile.Value == tileRef.Tile)
|
||||
if (!TryGetBiomeTile(indices, component.Layers, seed, grid, out var biomeTile) || biomeTile.Value == tileRef.Tile)
|
||||
continue;
|
||||
|
||||
tiles.Add((indices, biomeTile.Value));
|
||||
}
|
||||
}
|
||||
|
||||
grid.SetTiles(tiles);
|
||||
_mapSystem.SetTiles(gridUid, grid, tiles);
|
||||
tiles.Clear();
|
||||
|
||||
// Now do entities
|
||||
@@ -617,14 +643,14 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetEntity(indices, component.Layers, noise, grid, out var entPrototype))
|
||||
if (anchored.MoveNext(out _) || !TryGetEntity(indices, component, grid, out var entPrototype))
|
||||
continue;
|
||||
|
||||
// TODO: Fix non-anchored ents spawning.
|
||||
// Just track loaded chunks for now.
|
||||
var ent = Spawn(entPrototype, grid.GridTileToLocal(indices));
|
||||
var ent = Spawn(entPrototype, _mapSystem.GridTileToLocal(gridUid, grid, indices));
|
||||
|
||||
// At least for now unless we do lookups or smth, only work with anchoring.
|
||||
if (xformQuery.TryGetComponent(ent, out var xform) && !xform.Anchored)
|
||||
@@ -650,9 +676,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
continue;
|
||||
|
||||
// Don't mess with anything that's potentially anchored.
|
||||
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
|
||||
var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
|
||||
|
||||
if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, noise, grid, out var decals))
|
||||
if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, seed, grid, out var decals))
|
||||
continue;
|
||||
|
||||
foreach (var decal in decals)
|
||||
@@ -683,7 +709,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
/// <summary>
|
||||
/// Handles all of the queued chunk unloads for a particular biome.
|
||||
/// </summary>
|
||||
private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, FastNoiseLite noise)
|
||||
private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, int seed)
|
||||
{
|
||||
var active = _activeChunks[component];
|
||||
List<(Vector2i, Tile)>? tiles = null;
|
||||
@@ -695,14 +721,14 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
// Unload NOW!
|
||||
tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
|
||||
UnloadChunk(component, gridUid, grid, chunk, noise, tiles);
|
||||
UnloadChunk(component, gridUid, grid, chunk, seed, tiles);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads a specific biome chunk.
|
||||
/// </summary>
|
||||
private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, FastNoiseLite noise, List<(Vector2i, Tile)> tiles)
|
||||
private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, int seed, List<(Vector2i, Tile)> tiles)
|
||||
{
|
||||
// Reverse order to loading
|
||||
component.ModifiedTiles.TryGetValue(chunk, out var modified);
|
||||
@@ -735,7 +761,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
|
||||
// It's moved
|
||||
var entTile = grid.LocalToTile(xform.Coordinates);
|
||||
var entTile = _mapSystem.LocalToTile(gridUid, grid, xform.Coordinates);
|
||||
|
||||
if (!xform.Anchored || entTile != tile)
|
||||
{
|
||||
@@ -775,8 +801,8 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
|
||||
// If it's default data unload the tile.
|
||||
if (!TryGetBiomeTile(indices, component.Layers, noise, null, out var biomeTile) ||
|
||||
grid.TryGetTileRef(indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
|
||||
if (!TryGetBiomeTile(indices, component.Layers, seed, null, out var biomeTile) ||
|
||||
_mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
|
||||
{
|
||||
modified.Add(indices);
|
||||
continue;
|
||||
@@ -801,4 +827,51 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Creates a simple planet setup for a map.
|
||||
/// </summary>
|
||||
public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, int? seed = null, MetaDataComponent? metadata = null)
|
||||
{
|
||||
if (!Resolve(mapUid, ref metadata))
|
||||
return;
|
||||
|
||||
var biome = EnsureComp<BiomeComponent>(mapUid);
|
||||
seed ??= _random.Next();
|
||||
SetSeed(biome, seed.Value);
|
||||
SetTemplate(biome, biomeTemplate);
|
||||
Dirty(mapUid, biome, metadata);
|
||||
|
||||
var gravity = EnsureComp<GravityComponent>(mapUid);
|
||||
gravity.Enabled = true;
|
||||
gravity.Inherent = true;
|
||||
Dirty(mapUid, gravity, metadata);
|
||||
|
||||
// Day lighting
|
||||
// Daylight: #D8B059
|
||||
// Midday: #E6CB8B
|
||||
// Moonlight: #2b3143
|
||||
// Lava: #A34931
|
||||
|
||||
var light = EnsureComp<MapLightComponent>(mapUid);
|
||||
light.AmbientLightColor = Color.FromHex("#D8B059");
|
||||
Dirty(mapUid, light, metadata);
|
||||
|
||||
// Atmos
|
||||
var atmos = EnsureComp<MapAtmosphereComponent>(mapUid);
|
||||
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
|
||||
var mixture = new GasMixture(2500)
|
||||
{
|
||||
Temperature = 293.15f,
|
||||
Moles = moles,
|
||||
};
|
||||
|
||||
_atmos.SetMapAtmosphere(mapUid, false, mixture, atmos);
|
||||
|
||||
EnsureComp<MapGridComponent>(mapUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Decals;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Procedural;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -48,6 +49,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
_console.RegisterCommand("dungen_preset_vis", Loc.GetString("cmd-dungen_preset_vis-desc"), Loc.GetString("cmd-dungen_preset_vis-help"), DungeonPresetVis, PresetCallback);
|
||||
_console.RegisterCommand("dungen_pack_vis", Loc.GetString("cmd-dungen_pack_vis-desc"), Loc.GetString("cmd-dungen_pack_vis-help"), DungeonPackVis, PackCallback);
|
||||
_prototype.PrototypesReloaded += PrototypeReload;
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundCleanup);
|
||||
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
_dungeonJobQueue.Process();
|
||||
}
|
||||
|
||||
private void OnRoundStart(RoundStartingEvent ev)
|
||||
private void OnRoundCleanup(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
foreach (var token in _dungeonJobs.Values)
|
||||
{
|
||||
@@ -65,6 +67,10 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
}
|
||||
|
||||
_dungeonJobs.Clear();
|
||||
}
|
||||
|
||||
private void OnRoundStart(RoundStartingEvent ev)
|
||||
{
|
||||
var query = AllEntityQuery<DungeonAtlasTemplateComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out _))
|
||||
@@ -192,7 +198,6 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
|
||||
_dungeonJobs.Add(job, cancelToken);
|
||||
_dungeonJobQueue.EnqueueJob(job);
|
||||
job.Run();
|
||||
}
|
||||
|
||||
public async Task<Dungeon> GenerateDungeonAsync(
|
||||
|
||||
8
Content.Server/Salvage/RestrictedRangeSystem.cs
Normal file
8
Content.Server/Salvage/RestrictedRangeSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Salvage;
|
||||
|
||||
namespace Content.Server.Salvage;
|
||||
|
||||
public sealed class RestrictedRangeSystem : SharedRestrictedRangeSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -196,7 +196,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
if (!lootProto.Guaranteed)
|
||||
continue;
|
||||
|
||||
await SpawnDungeonLoot(dungeon, missionBiome, lootProto, mapUid, grid, random, reservedTiles);
|
||||
await SpawnDungeonLoot(lootProto, mapUid);
|
||||
}
|
||||
|
||||
// Handle boss loot (when relevant).
|
||||
@@ -298,7 +298,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
// oh noooooooooooo
|
||||
}
|
||||
|
||||
private async Task SpawnDungeonLoot(Dungeon dungeon, SalvageBiomeModPrototype biomeMod, SalvageLootPrototype loot, EntityUid gridUid, MapGridComponent grid, Random random, List<Vector2i> reservedTiles)
|
||||
private async Task SpawnDungeonLoot(SalvageLootPrototype loot, EntityUid gridUid)
|
||||
{
|
||||
for (var i = 0; i < loot.LootRules.Count; i++)
|
||||
{
|
||||
@@ -308,10 +308,9 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
{
|
||||
case BiomeMarkerLoot biomeLoot:
|
||||
{
|
||||
if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome) &&
|
||||
biomeLoot.Prototype.TryGetValue(biomeMod.ID, out var mod))
|
||||
if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
|
||||
{
|
||||
_biome.AddMarkerLayer(biome, mod);
|
||||
_biome.AddMarkerLayer(biome, biomeLoot.Prototype);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Gateway;
|
||||
|
||||
@@ -26,7 +27,7 @@ public sealed class GatewayBoundUserInterfaceState : BoundUserInterfaceState
|
||||
/// <summary>
|
||||
/// List of enabled destinations and information about them.
|
||||
/// </summary>
|
||||
public readonly List<(NetEntity, string, TimeSpan, bool)> Destinations;
|
||||
public readonly List<GatewayDestinationData> Destinations;
|
||||
|
||||
/// <summary>
|
||||
/// Which destination it is currently linked to, if any.
|
||||
@@ -34,25 +35,52 @@ public sealed class GatewayBoundUserInterfaceState : BoundUserInterfaceState
|
||||
public readonly NetEntity? Current;
|
||||
|
||||
/// <summary>
|
||||
/// Time the portal will close at.
|
||||
/// Next time the portal is ready to be used.
|
||||
/// </summary>
|
||||
public readonly TimeSpan NextClose;
|
||||
public readonly TimeSpan NextReady;
|
||||
|
||||
public readonly TimeSpan Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// Time the portal last opened at.
|
||||
/// Next time the destination generator unlocks another destination.
|
||||
/// </summary>
|
||||
public readonly TimeSpan LastOpen;
|
||||
public readonly TimeSpan NextUnlock;
|
||||
|
||||
public GatewayBoundUserInterfaceState(List<(NetEntity, string, TimeSpan, bool)> destinations,
|
||||
NetEntity? current, TimeSpan nextClose, TimeSpan lastOpen)
|
||||
/// <summary>
|
||||
/// How long an unlock takes.
|
||||
/// </summary>
|
||||
public readonly TimeSpan UnlockTime;
|
||||
|
||||
public GatewayBoundUserInterfaceState(List<GatewayDestinationData> destinations,
|
||||
NetEntity? current, TimeSpan nextReady, TimeSpan cooldown, TimeSpan nextUnlock, TimeSpan unlockTime)
|
||||
{
|
||||
Destinations = destinations;
|
||||
Current = current;
|
||||
NextClose = nextClose;
|
||||
LastOpen = lastOpen;
|
||||
NextReady = nextReady;
|
||||
Cooldown = cooldown;
|
||||
NextUnlock = nextUnlock;
|
||||
UnlockTime = unlockTime;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public record struct GatewayDestinationData
|
||||
{
|
||||
public NetEntity Entity;
|
||||
|
||||
public FormattedMessage Name;
|
||||
|
||||
/// <summary>
|
||||
/// Is the portal currently open.
|
||||
/// </summary>
|
||||
public bool Portal;
|
||||
|
||||
/// <summary>
|
||||
/// Is the map the gateway on locked or unlocked.
|
||||
/// </summary>
|
||||
public bool Locked;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GatewayOpenPortalMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
|
||||
12
Content.Shared/Gateway/SharedGatewayGeneratorSystem.cs
Normal file
12
Content.Shared/Gateway/SharedGatewayGeneratorSystem.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Gateway;
|
||||
|
||||
/// <summary>
|
||||
/// Sent from client to server upon taking a gateway destination.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GatewayDestinationMessage : EntityEventArgs
|
||||
{
|
||||
public int Index;
|
||||
}
|
||||
13
Content.Shared/Movement/Components/BoundaryComponent.cs
Normal file
13
Content.Shared/Movement/Components/BoundaryComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Movement.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a boundary that can bump someone back when touched.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class BoundaryComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Offset = 2f;
|
||||
}
|
||||
@@ -11,8 +11,6 @@ namespace Content.Shared.Parallax.Biomes;
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedBiomeSystem))]
|
||||
public sealed partial class BiomeComponent : Component
|
||||
{
|
||||
public FastNoiseLite Noise = new();
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("seed")]
|
||||
[AutoNetworkedField]
|
||||
public int Seed = -1;
|
||||
|
||||
@@ -11,14 +11,17 @@ public sealed class BiomeMarkerLayerPrototype : IBiomeMarkerLayer
|
||||
{
|
||||
[IdDataField] public string ID { get; } = default!;
|
||||
|
||||
[DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Prototype { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Checks for the relevant entity for the tile before spawning. Useful for substituting walls with ore veins for example.
|
||||
/// </summary>
|
||||
[DataField("entityMask", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? EntityMask { get; private set; }
|
||||
[DataField]
|
||||
public Dictionary<EntProtoId, EntProtoId> EntityMask { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Default prototype to spawn. If null will fall back to entity mask.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? Prototype { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum radius between 2 points
|
||||
@@ -33,10 +36,16 @@ public sealed class BiomeMarkerLayerPrototype : IBiomeMarkerLayer
|
||||
public int MaxCount = int.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// How many mobs to spawn in one group.
|
||||
/// Minimum entities to spawn in one group.
|
||||
/// </summary>
|
||||
[DataField("groupCount")]
|
||||
public int GroupCount = 1;
|
||||
[DataField]
|
||||
public int MinGroupSize = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum entities to spawn in one group.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int MaxGroupSize = 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField("size")]
|
||||
|
||||
@@ -11,9 +11,9 @@ public interface IBiomeMarkerLayer : IPrototype
|
||||
/// <summary>
|
||||
/// Biome template to use as a mask for this layer.
|
||||
/// </summary>
|
||||
public string? EntityMask { get; }
|
||||
public Dictionary<EntProtoId, EntProtoId> EntityMask { get; }
|
||||
|
||||
public string Prototype { get; }
|
||||
public string? Prototype { get; }
|
||||
|
||||
/// <summary>
|
||||
/// How large the pre-generated points area is.
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
@@ -13,21 +14,11 @@ namespace Content.Shared.Parallax.Biomes;
|
||||
public abstract class SharedBiomeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager ProtoManager = default!;
|
||||
[Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[Dependency] protected readonly ITileDefinitionManager TileDefManager = default!;
|
||||
|
||||
protected const byte ChunkSize = 8;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BiomeComponent, AfterAutoHandleStateEvent>(OnBiomeAfterHandleState);
|
||||
}
|
||||
|
||||
private void OnBiomeAfterHandleState(EntityUid uid, BiomeComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
component.Noise.SetSeed(component.Seed);
|
||||
}
|
||||
|
||||
private T Pick<T>(List<T> collection, float value)
|
||||
{
|
||||
// Listen I don't need this exact and I'm too lazy to finetune just for random ent picking.
|
||||
@@ -89,13 +80,13 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetBiomeTile(indices, biome.Layers, biome.Noise, grid, out tile);
|
||||
return TryGetBiomeTile(indices, biome.Layers, biome.Seed, grid, out tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the tile, real or otherwise, for the specified indices.
|
||||
/// </summary>
|
||||
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, FastNoiseLite noise, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (grid?.TryGetTileRef(indices, out var tileRef) == true && !tileRef.Tile.IsEmpty)
|
||||
{
|
||||
@@ -103,23 +94,23 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldSeed = noise.GetSeed();
|
||||
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = layers[i];
|
||||
var noiseCopy = GetNoise(layer.Noise, seed);
|
||||
|
||||
var invert = layer.Invert;
|
||||
var value = noiseCopy.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
continue;
|
||||
|
||||
// Check if the tile is from meta layer, otherwise fall back to default layers.
|
||||
if (layer is BiomeMetaLayer meta)
|
||||
{
|
||||
SetNoise(noise, oldSeed, layer.Noise);
|
||||
var found = noise.GetNoise(indices.X, indices.Y);
|
||||
found *= layer.Invert ? -1 : 1;
|
||||
|
||||
if (found > layer.Threshold && TryGetBiomeTile(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, noise,
|
||||
grid, out tile))
|
||||
if (TryGetBiomeTile(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out tile))
|
||||
{
|
||||
noise.SetSeed(oldSeed);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -129,16 +120,12 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
if (layer is not BiomeTileLayer tileLayer)
|
||||
continue;
|
||||
|
||||
SetNoise(noise, oldSeed, layer.Noise);
|
||||
|
||||
if (TryGetTile(indices, noise, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index<ContentTileDefinition>(tileLayer.Tile), tileLayer.Variants, out tile))
|
||||
if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index<ContentTileDefinition>(tileLayer.Tile), tileLayer.Variants, out tile))
|
||||
{
|
||||
noise.SetSeed(oldSeed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
noise.SetSeed(oldSeed);
|
||||
tile = null;
|
||||
return false;
|
||||
}
|
||||
@@ -146,9 +133,9 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
||||
/// </summary>
|
||||
private bool TryGetTile(Vector2i indices, FastNoiseLite seed, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
|
||||
private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
var found = seed.GetNoise(indices.X, indices.Y);
|
||||
var found = noise.GetNoise(indices.X, indices.Y);
|
||||
found = invert ? found * -1 : found;
|
||||
|
||||
if (found < threshold)
|
||||
@@ -163,7 +150,7 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
// Pick a variant tile if they're available as well
|
||||
if (variantCount > 1)
|
||||
{
|
||||
var variantValue = (seed.GetNoise(indices.X * 8, indices.Y * 8, variantCount) + 1f) / 2f;
|
||||
var variantValue = (noise.GetNoise(indices.X * 8, indices.Y * 8, variantCount) + 1f) / 2f;
|
||||
variant = (byte) Pick(variantCount, variantValue);
|
||||
|
||||
if (variants != null)
|
||||
@@ -179,23 +166,28 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Tries to get the relevant entity for this tile.
|
||||
/// </summary>
|
||||
protected bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, FastNoiseLite noise, MapGridComponent grid,
|
||||
public bool TryGetEntity(Vector2i indices, BiomeComponent component, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, layers, noise, grid, out var tileRef))
|
||||
if (!TryGetBiomeTile(indices, component.Layers, component.Seed, grid, out var tile))
|
||||
{
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tileId = TileDefManager[tileRef.Value.TypeId].ID;
|
||||
var oldSeed = noise.GetSeed();
|
||||
return TryGetEntity(indices, component.Layers, tile.Value, component.Seed, grid, out entity);
|
||||
}
|
||||
|
||||
|
||||
private bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
var tileId = TileDefManager[tileRef.TypeId].ID;
|
||||
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = layers[i];
|
||||
|
||||
// Decals might block entity so need to check if there's one in front of us.
|
||||
switch (layer)
|
||||
{
|
||||
case BiomeDummyLayer:
|
||||
@@ -211,9 +203,10 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
continue;
|
||||
}
|
||||
|
||||
SetNoise(noise, oldSeed, layer.Noise);
|
||||
var noiseCopy = GetNoise(layer.Noise, seed);
|
||||
|
||||
var invert = layer.Invert;
|
||||
var value = noise.GetNoise(indices.X, indices.Y);
|
||||
var value = noiseCopy.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
@@ -221,29 +214,26 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
|
||||
if (layer is BiomeMetaLayer meta)
|
||||
{
|
||||
if (TryGetEntity(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, noise, grid, out entity))
|
||||
if (TryGetEntity(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, tileRef, seed, grid, out entity))
|
||||
{
|
||||
noise.SetSeed(oldSeed);
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decals might block entity so need to check if there's one in front of us.
|
||||
if (layer is not BiomeEntityLayer biomeLayer)
|
||||
{
|
||||
entity = null;
|
||||
noise.SetSeed(oldSeed);
|
||||
return false;
|
||||
}
|
||||
|
||||
var noiseValue = noise.GetNoise(indices.X, indices.Y, i);
|
||||
var noiseValue = noiseCopy.GetNoise(indices.X, indices.Y, i);
|
||||
entity = Pick(biomeLayer.Entities, (noiseValue + 1f) / 2f);
|
||||
noise.SetSeed(oldSeed);
|
||||
return true;
|
||||
}
|
||||
|
||||
noise.SetSeed(oldSeed);
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
@@ -251,17 +241,16 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Tries to get the relevant decals for this tile.
|
||||
/// </summary>
|
||||
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, FastNoiseLite noise, MapGridComponent grid,
|
||||
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, layers, noise, grid, out var tileRef))
|
||||
if (!TryGetBiomeTile(indices, layers, seed, grid, out var tileRef))
|
||||
{
|
||||
decals = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tileId = TileDefManager[tileRef.Value.TypeId].ID;
|
||||
var oldSeed = noise.GetSeed();
|
||||
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -283,17 +272,18 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
continue;
|
||||
}
|
||||
|
||||
SetNoise(noise, oldSeed, layer.Noise);
|
||||
var invert = layer.Invert;
|
||||
var noiseCopy = GetNoise(layer.Noise, seed);
|
||||
var value = noiseCopy.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
continue;
|
||||
|
||||
if (layer is BiomeMetaLayer meta)
|
||||
{
|
||||
var found = noise.GetNoise(indices.X, indices.Y);
|
||||
found *= layer.Invert ? -1 : 1;
|
||||
|
||||
if (found > layer.Threshold && TryGetDecals(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, noise, grid, out decals))
|
||||
if (TryGetDecals(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out decals))
|
||||
{
|
||||
noise.SetSeed(oldSeed);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -303,14 +293,7 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
// Check if the other layer should even render, if not then keep going.
|
||||
if (layer is not BiomeDecalLayer decalLayer)
|
||||
{
|
||||
var value = noise.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
continue;
|
||||
|
||||
decals = null;
|
||||
noise.SetSeed(oldSeed);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -321,13 +304,13 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
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.GetNoise(index.X, index.Y);
|
||||
var decalValue = noiseCopy.GetNoise(index.X, index.Y);
|
||||
decalValue = invert ? decalValue * -1 : decalValue;
|
||||
|
||||
if (decalValue < decalLayer.Threshold)
|
||||
continue;
|
||||
|
||||
decals.Add((Pick(decalLayer.Decals, (noise.GetNoise(indices.X, indices.Y, x + y * decalLayer.Divisions) + 1f) / 2f), index));
|
||||
decals.Add((Pick(decalLayer.Decals, (noiseCopy.GetNoise(indices.X, indices.Y, x + y * decalLayer.Divisions) + 1f) / 2f), index));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,34 +318,20 @@ public abstract class SharedBiomeSystem : EntitySystem
|
||||
if (decals.Count == 0)
|
||||
continue;
|
||||
|
||||
noise.SetSeed(oldSeed);
|
||||
return true;
|
||||
}
|
||||
|
||||
noise.SetSeed(oldSeed);
|
||||
decals = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetNoise(FastNoiseLite noise, int oldSeed, FastNoiseLite data)
|
||||
private FastNoiseLite GetNoise(FastNoiseLite seedNoise, int seed)
|
||||
{
|
||||
// General
|
||||
noise.SetSeed(oldSeed + data.GetSeed());
|
||||
noise.SetFrequency(data.GetFrequency());
|
||||
noise.SetNoiseType(data.GetNoiseType());
|
||||
|
||||
noise.GetRotationType3D();
|
||||
|
||||
// Fractal
|
||||
noise.SetFractalType(data.GetFractalType());
|
||||
noise.SetFractalOctaves(data.GetFractalOctaves());
|
||||
noise.SetFractalLacunarity(data.GetFractalLacunarity());
|
||||
|
||||
// Cellular
|
||||
noise.SetCellularDistanceFunction(data.GetCellularDistanceFunction());
|
||||
noise.SetCellularReturnType(data.GetCellularReturnType());
|
||||
noise.SetCellularJitter(data.GetCellularJitter());
|
||||
|
||||
// Domain warps require separate noise
|
||||
var noiseCopy = new FastNoiseLite();
|
||||
_serManager.CopyTo(seedNoise, ref noiseCopy, notNullableOverride: true);
|
||||
noiseCopy.SetSeed(noiseCopy.GetSeed() + seed);
|
||||
// Ensure re-calculate is run.
|
||||
noiseCopy.SetFractalOctaves(noiseCopy.GetFractalOctaves());
|
||||
return noiseCopy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
@@ -9,7 +10,6 @@ namespace Content.Shared.Procedural.Loot;
|
||||
/// </summary>
|
||||
public sealed partial class BiomeMarkerLoot : IDungeonLoot
|
||||
{
|
||||
[DataField("proto", required: true,
|
||||
customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer<string, BiomeMarkerLayerPrototype>))]
|
||||
public Dictionary<string, string> Prototype = new();
|
||||
[DataField("proto", required: true)]
|
||||
public ProtoId<BiomeMarkerLayerPrototype> Prototype = new();
|
||||
}
|
||||
|
||||
17
Content.Shared/Salvage/RestrictedRangeComponent.cs
Normal file
17
Content.Shared/Salvage/RestrictedRangeComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Salvage;
|
||||
|
||||
/// <summary>
|
||||
/// Restricts entities to the specified range on the attached map entity.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class RestrictedRangeComponent : Component
|
||||
{
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public float Range = 72f;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public Vector2 Origin;
|
||||
}
|
||||
6
Content.Shared/Salvage/SharedRestrictedRangeSystem.cs
Normal file
6
Content.Shared/Salvage/SharedRestrictedRangeSystem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Content.Shared.Salvage;
|
||||
|
||||
public abstract class SharedRestrictedRangeSystem : EntitySystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -47,4 +47,10 @@ public sealed partial class PortalComponent : Component
|
||||
/// </remarks>
|
||||
[DataField("maxTeleportRadius")]
|
||||
public float? MaxTeleportRadius;
|
||||
|
||||
/// <summary>
|
||||
/// Should we teleport randomly if nothing is linked.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool RandomTeleport = true;
|
||||
}
|
||||
|
||||
@@ -54,13 +54,29 @@ public sealed class LinkedEntitySystem : EntitySystem
|
||||
_appearance.SetData(first, LinkedEntityVisuals.HasAnyLinks, true);
|
||||
_appearance.SetData(second, LinkedEntityVisuals.HasAnyLinks, true);
|
||||
|
||||
Dirty(firstLink);
|
||||
Dirty(secondLink);
|
||||
Dirty(first, firstLink);
|
||||
Dirty(second, secondLink);
|
||||
|
||||
return firstLink.LinkedEntities.Add(second)
|
||||
&& secondLink.LinkedEntities.Add(first);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does a one-way link from source to target.
|
||||
/// </summary>
|
||||
/// <param name="deleteOnEmptyLinks">Whether both entities should now delete once their links are removed</param>
|
||||
public bool OneWayLink(EntityUid source, EntityUid target, bool deleteOnEmptyLinks=false)
|
||||
{
|
||||
var firstLink = EnsureComp<LinkedEntityComponent>(source);
|
||||
firstLink.DeleteOnEmptyLinks = deleteOnEmptyLinks;
|
||||
|
||||
_appearance.SetData(source, LinkedEntityVisuals.HasAnyLinks, true);
|
||||
|
||||
Dirty(source, firstLink);
|
||||
|
||||
return firstLink.LinkedEntities.Add(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlinks two entities. Deletes either entity if <see cref="LinkedEntityComponent.DeleteOnEmptyLinks"/>
|
||||
/// was true and its links are now empty. Symmetrical, so order doesn't matter.
|
||||
|
||||
@@ -131,7 +131,7 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
// if target is a portal, signal that they shouldn't be immediately portaled back
|
||||
var timeout = EnsureComp<PortalTimeoutComponent>(subject);
|
||||
timeout.EnteredPortal = uid;
|
||||
Dirty(timeout);
|
||||
Dirty(subject, timeout);
|
||||
}
|
||||
|
||||
TeleportEntity(uid, subject, Transform(target).Coordinates, target);
|
||||
@@ -142,6 +142,7 @@ public abstract class SharedPortalSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// no linked entity--teleport randomly
|
||||
if (component.RandomTeleport)
|
||||
TeleportRandomly(uid, subject, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
gateway-window-title = Gateway
|
||||
gateway-window-ready = Ready!
|
||||
gateway-window-ready-in = Ready in: {$time}s
|
||||
gateway-window-already-active = Already active
|
||||
gateway-window-open-portal = Open Portal
|
||||
gateway-window-no-destinations = No destinations found.
|
||||
gateway-window-portal-closing = Portal closing
|
||||
gateway-window-portal-cooldown = Cooldown
|
||||
gateway-window-portal-unlock = Next unlock
|
||||
gateway-window-locked = Locked
|
||||
|
||||
gateway-access-denied = Access denied!
|
||||
gateway-close-portal = Close Portal
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
components:
|
||||
- type: StationArrivals
|
||||
|
||||
- type: entity
|
||||
id: BaseStationGateway
|
||||
abstract: true
|
||||
components:
|
||||
- type: GatewayGenerator
|
||||
|
||||
|
||||
- type: entity
|
||||
id: BaseStationShuttles
|
||||
abstract: true
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
- BaseStationJobsSpawning
|
||||
- BaseStationRecords
|
||||
- BaseStationArrivals
|
||||
- BaseStationGateway
|
||||
- BaseStationShuttles
|
||||
- BaseStationCentcomm
|
||||
- BaseStationEvacuation
|
||||
|
||||
@@ -50,20 +50,8 @@
|
||||
components:
|
||||
- type: ActivatableUI
|
||||
key: enum.GatewayUiKey.Key
|
||||
- type: ActivatableUIRequiresPower
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.GatewayUiKey.Key
|
||||
type: GatewayBoundUserInterface
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 3000
|
||||
- type: ExtensionCableReceiver
|
||||
- type: Gateway
|
||||
|
||||
- type: entity
|
||||
parent: BaseGateway
|
||||
id: GatewayDestination
|
||||
suffix: Destination
|
||||
components:
|
||||
- type: GatewayDestination
|
||||
name: Unknown
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
- type: biomeMarkerLayer
|
||||
id: Lizards
|
||||
proto: MobLizard
|
||||
groupCount: 5
|
||||
prototype: MobLizard
|
||||
minGroupSize: 3
|
||||
maxGroupSize: 5
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: WatchersLavaland
|
||||
proto: MobWatcherLavaland
|
||||
groupCount: 3
|
||||
prototype: MobWatcherLavaland
|
||||
minGroupSize: 3
|
||||
maxGroupSize: 3
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: WatchersIcewing
|
||||
proto: MobWatcherIcewing
|
||||
groupCount: 3
|
||||
prototype: MobWatcherIcewing
|
||||
minGroupSize: 3
|
||||
maxGroupSize: 3
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: WatchersMagmawing
|
||||
proto: MobWatcherMagmawing
|
||||
groupCount: 3
|
||||
prototype: MobWatcherMagmawing
|
||||
minGroupSize: 3
|
||||
maxGroupSize: 3
|
||||
|
||||
# TODO: Needs to be more robust
|
||||
- type: biomeMarkerLayer
|
||||
id: Xenos
|
||||
proto: MobXeno
|
||||
prototype: MobXeno
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: Carps
|
||||
proto: MobCarpDungeon
|
||||
prototype: MobCarpDungeon
|
||||
|
||||
|
||||
#- type: biomeMarkerLayer
|
||||
|
||||
@@ -1,290 +1,103 @@
|
||||
# Low value
|
||||
- type: biomeMarkerLayer
|
||||
id: OreTin
|
||||
proto: WallRockTin
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockTin
|
||||
WallRockBasalt: WallRockBasaltTin
|
||||
WallRockChromite: WallRockChromiteTin
|
||||
WallRockSnow: WallRockSnowTin
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
minGroupSize: 10
|
||||
maxGroupSize: 20
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: OreQuartz
|
||||
proto: WallRockQuartz
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockQuartz
|
||||
WallRockBasalt: WallRockBasaltQuartz
|
||||
WallRockChromite: WallRockChromiteQuartz
|
||||
WallRockSnow: WallRockSnowQuartz
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
minGroupSize: 10
|
||||
maxGroupSize: 20
|
||||
radius: 4
|
||||
|
||||
# Medium value
|
||||
# Gold
|
||||
- type: biomeMarkerLayer
|
||||
id: OreGold
|
||||
proto: WallRockGold
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockGold
|
||||
WallRockBasalt: WallRockBasaltGold
|
||||
WallRockChromite: WallRockChromiteGold
|
||||
WallRockSnow: WallRockSnowGold
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
minGroupSize: 5
|
||||
maxGroupSize: 10
|
||||
radius: 4
|
||||
|
||||
# Silver
|
||||
- type: biomeMarkerLayer
|
||||
id: OreSilver
|
||||
proto: WallRockSilver
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockSilver
|
||||
WallRockBasalt: WallRockBasaltSilver
|
||||
WallRockChromite: WallRockChromiteSilver
|
||||
WallRockSnow: WallRockSnowSilver
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
minGroupSize: 5
|
||||
maxGroupSize: 10
|
||||
radius: 4
|
||||
|
||||
# High value
|
||||
# Plasma
|
||||
- type: biomeMarkerLayer
|
||||
id: OrePlasma
|
||||
proto: WallRockPlasma
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockPlasma
|
||||
WallRockBasalt: WallRockBasaltPlasma
|
||||
WallRockChromite: WallRockChromitePlasma
|
||||
WallRockSnow: WallRockSnowPlasma
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
minGroupSize: 5
|
||||
maxGroupSize: 10
|
||||
radius: 4
|
||||
|
||||
# Uranium
|
||||
- type: biomeMarkerLayer
|
||||
id: OreUranium
|
||||
proto: WallRockUranium
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockUranium
|
||||
WallRockBasalt: WallRockBasaltUranium
|
||||
WallRockChromite: WallRockChromiteUranium
|
||||
WallRockSnow: WallRockSnowUranium
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
minGroupSize: 5
|
||||
maxGroupSize: 10
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: OreBananium
|
||||
proto: WallRockBananium
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockBananium
|
||||
WallRockBasalt: WallRockBasaltBananium
|
||||
WallRockChromite: WallRockChromiteBananium
|
||||
WallRockSnow: WallRockSnowBananium
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
minGroupSize: 5
|
||||
maxGroupSize: 10
|
||||
radius: 4
|
||||
|
||||
# Artifact Fragment
|
||||
- type: biomeMarkerLayer
|
||||
id: OreArtifactFragment
|
||||
proto: WallRockArtifactFragment
|
||||
entityMask: WallRock
|
||||
entityMask:
|
||||
WallRock: WallRockArtifactFragment
|
||||
WallRockBasalt: WallRockBasaltArtifactFragment
|
||||
WallRockChromite: WallRockChromiteArtifactFragment
|
||||
WallRockSnow: WallRockSnowArtifactFragment
|
||||
maxCount: 6
|
||||
groupCount: 1
|
||||
radius: 4
|
||||
|
||||
# Basalt variant
|
||||
# Low value
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOreTin
|
||||
proto: WallRockBasaltTin
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOreQuartz
|
||||
proto: WallRockBasaltQuartz
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
radius: 4
|
||||
|
||||
# Medium value
|
||||
# Gold
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOreGold
|
||||
proto: WallRockBasaltGold
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Silver
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOreSilver
|
||||
proto: WallRockBasaltSilver
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# High value
|
||||
# Plasma
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOrePlasma
|
||||
proto: WallRockBasaltPlasma
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Uranium
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOreUranium
|
||||
proto: WallRockBasaltUranium
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOreBananium
|
||||
proto: WallRockBasaltBananium
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Artifact Fragment
|
||||
- type: biomeMarkerLayer
|
||||
id: BasaltOreArtifactFragment
|
||||
proto: WallRockBasaltArtifactFragment
|
||||
entityMask: WallRockBasalt
|
||||
maxCount: 6
|
||||
groupCount: 1
|
||||
radius: 4
|
||||
|
||||
# Shadow basalt variant
|
||||
# Low value
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOreTin
|
||||
proto: WallRockChromiteTin
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOreQuartz
|
||||
proto: WallRockChromiteQuartz
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
radius: 4
|
||||
|
||||
# Medium value
|
||||
# Gold
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOreGold
|
||||
proto: WallRockChromiteGold
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Silver
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOreSilver
|
||||
proto: WallRockChromiteSilver
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# High value
|
||||
# Plasma
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOrePlasma
|
||||
proto: WallRockChromitePlasma
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Uranium
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOreUranium
|
||||
proto: WallRockChromiteUranium
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOreBananium
|
||||
proto: WallRockChromiteBananium
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Artifact Fragment
|
||||
- type: biomeMarkerLayer
|
||||
id: ChromiteOreArtifactFragment
|
||||
proto: WallRockChromiteArtifactFragment
|
||||
entityMask: WallRockChromite
|
||||
maxCount: 6
|
||||
groupCount: 1
|
||||
radius: 4
|
||||
|
||||
# Snow variant
|
||||
# Low value
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOreTin
|
||||
proto: WallRockSnowTin
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOreQuartz
|
||||
proto: WallRockSnowQuartz
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 30
|
||||
groupCount: 10
|
||||
radius: 4
|
||||
|
||||
# Medium value
|
||||
# Gold
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOreGold
|
||||
proto: WallRockSnowGold
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Silver
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOreSilver
|
||||
proto: WallRockSnowSilver
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 30
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# High value
|
||||
# Plasma
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOrePlasma
|
||||
proto: WallRockSnowPlasma
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Uranium
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOreUranium
|
||||
proto: WallRockSnowUranium
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOreBananium
|
||||
proto: WallRockSnowBananium
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 12
|
||||
groupCount: 5
|
||||
radius: 4
|
||||
|
||||
# Artifact Fragment
|
||||
- type: biomeMarkerLayer
|
||||
id: SnowOreArtifactFragment
|
||||
proto: WallRockSnowArtifactFragment
|
||||
entityMask: WallRockSnow
|
||||
maxCount: 6
|
||||
groupCount: 1
|
||||
minGroupSize: 1
|
||||
maxGroupSize: 2
|
||||
radius: 4
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
fractalType: FBm
|
||||
octaves: 2
|
||||
lacunarity: 2
|
||||
gain: 0.5
|
||||
- !type:BiomeMetaLayer
|
||||
template: Grasslands
|
||||
threshold: 0
|
||||
@@ -23,7 +22,6 @@
|
||||
fractalType: FBm
|
||||
octaves: 2
|
||||
lacunarity: 2
|
||||
gain: 0.5
|
||||
- !type:BiomeMetaLayer
|
||||
template: Snow
|
||||
threshold: 0.5
|
||||
@@ -33,7 +31,6 @@
|
||||
fractalType: FBm
|
||||
octaves: 2
|
||||
lacunarity: 2
|
||||
gain: 0.5
|
||||
|
||||
# Desert
|
||||
# TODO: Water in desert
|
||||
@@ -62,7 +59,6 @@
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
lacunarity: 2
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
allowedTiles:
|
||||
@@ -129,7 +125,6 @@
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
lacunarity: 2
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
decals:
|
||||
@@ -174,7 +169,6 @@
|
||||
lacunarity: 2
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
entities:
|
||||
@@ -229,7 +223,6 @@
|
||||
lacunarity: 2
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
|
||||
@@ -305,7 +298,6 @@
|
||||
lacunarity: 2
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
entities:
|
||||
@@ -370,7 +362,6 @@
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
lacunarity: 2
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
decals:
|
||||
@@ -436,7 +427,6 @@
|
||||
lacunarity: 2
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
entities:
|
||||
@@ -452,7 +442,6 @@
|
||||
lacunarity: 2
|
||||
fractalType: FBm
|
||||
octaves: 5
|
||||
gain: 1
|
||||
cellularDistanceFunction: Euclidean
|
||||
cellularReturnType: Distance2
|
||||
- !type:BiomeDummyLayer
|
||||
@@ -586,7 +575,7 @@
|
||||
fractalType: Ridged
|
||||
octaves: 1
|
||||
frequency: 0.1
|
||||
gain: 0
|
||||
gain: 0.5
|
||||
allowedTiles:
|
||||
- FloorAsteroidSand
|
||||
entities:
|
||||
|
||||
@@ -123,22 +123,14 @@
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OreTin
|
||||
Grasslands: OreTin
|
||||
Lava: BasaltOreTin
|
||||
Snow: SnowOreTin
|
||||
proto: OreTin
|
||||
|
||||
- type: salvageLoot
|
||||
id: OreQuartz
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OreQuartz
|
||||
Grasslands: OreQuartz
|
||||
Lava: BasaltOreQuartz
|
||||
Snow: SnowOreQuartz
|
||||
proto: OreQuartz
|
||||
|
||||
# - Medium value
|
||||
- type: salvageLoot
|
||||
@@ -146,22 +138,14 @@
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OreGold
|
||||
Grasslands: OreGold
|
||||
Lava: BasaltOreGold
|
||||
Snow: SnowOreGold
|
||||
proto: OreGold
|
||||
|
||||
- type: salvageLoot
|
||||
id: OreSilver
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OreSilver
|
||||
Grasslands: OreSilver
|
||||
Lava: BasaltOreSilver
|
||||
Snow: SnowOreSilver
|
||||
proto: OreSilver
|
||||
|
||||
# - High value
|
||||
- type: salvageLoot
|
||||
@@ -169,41 +153,25 @@
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OrePlasma
|
||||
Grasslands: OrePlasma
|
||||
Lava: BasaltOrePlasma
|
||||
Snow: SnowOrePlasma
|
||||
proto: OrePlasma
|
||||
|
||||
- type: salvageLoot
|
||||
id: OreUranium
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OreUranium
|
||||
Grasslands: OreUranium
|
||||
Lava: BasaltOreUranium
|
||||
Snow: SnowOreUranium
|
||||
proto: OreUranium
|
||||
|
||||
- type: salvageLoot
|
||||
id: OreBananium
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OreBananium
|
||||
Grasslands: OreBananium
|
||||
Lava: BasaltOreBananium
|
||||
Snow: SnowOreBananium
|
||||
proto: OreBananium
|
||||
|
||||
- type: salvageLoot
|
||||
id: OreArtifactFragment
|
||||
guaranteed: true
|
||||
loots:
|
||||
- !type:BiomeMarkerLoot
|
||||
proto:
|
||||
Caves: OreArtifactFragment
|
||||
Grasslands: OreArtifactFragment
|
||||
Lava: BasaltOreArtifactFragment
|
||||
Snow: SnowOreArtifactFragment
|
||||
proto: OreArtifactFragment
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
kind: source
|
||||
path: "/Textures/Shaders/gradient_circle_mask.swsl"
|
||||
|
||||
- type: shader
|
||||
id: WorldGradientCircle
|
||||
kind: source
|
||||
path: "/Textures/Shaders/world_gradient_circle.swsl"
|
||||
|
||||
- type: shader
|
||||
id: ColoredScreenBorder
|
||||
kind: source
|
||||
|
||||
27
Resources/Textures/Shaders/world_gradient_circle.swsl
Normal file
27
Resources/Textures/Shaders/world_gradient_circle.swsl
Normal file
@@ -0,0 +1,27 @@
|
||||
// Has 2 circles, an inner one that is unaffected and an outer one.
|
||||
light_mode unshaded;
|
||||
|
||||
const highp vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
// Position of the center in pixel terms.
|
||||
uniform highp vec2 position;
|
||||
uniform highp float maxRange;
|
||||
uniform highp float minRange;
|
||||
uniform highp float bufferRange;
|
||||
uniform highp float gradient;
|
||||
|
||||
void fragment() {
|
||||
highp float distance = length(FRAGCOORD.xy - position);
|
||||
|
||||
if (distance > maxRange) {
|
||||
discard;
|
||||
}
|
||||
else if (distance < minRange) {
|
||||
COLOR = color;
|
||||
}
|
||||
else {
|
||||
|
||||
highp float ratio = 1.0 - pow((distance - minRange) / bufferRange, gradient);
|
||||
COLOR = vec4(color.x, color.y, color.z, ratio);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@
|
||||
- toggledecals
|
||||
- nodevis
|
||||
- nodevisfilter
|
||||
- showbiome
|
||||
- net_draw_interp
|
||||
- showmeleespread
|
||||
- showgunspread
|
||||
|
||||
Reference in New Issue
Block a user