]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Gateway destinations (#21040)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Wed, 15 Nov 2023 02:23:40 +0000 (13:23 +1100)
committerGitHub <noreply@github.com>
Wed, 15 Nov 2023 02:23:40 +0000 (19:23 -0700)
* 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

51 files changed:
Content.Client/Gateway/UI/GatewayBoundUserInterface.cs
Content.Client/Gateway/UI/GatewayWindow.xaml
Content.Client/Gateway/UI/GatewayWindow.xaml.cs
Content.Client/Overlays/StencilOverlay.RestrictedRange.cs [new file with mode: 0644]
Content.Client/Overlays/StencilOverlay.Weather.cs [new file with mode: 0644]
Content.Client/Overlays/StencilOverlay.cs [new file with mode: 0644]
Content.Client/Overlays/StencilOverlaySystem.cs [new file with mode: 0644]
Content.Client/Parallax/BiomeDebugOverlay.cs [new file with mode: 0644]
Content.Client/Parallax/Commands/ShowBiomeCommand.cs [new file with mode: 0644]
Content.Client/Parallax/ParallaxSystem.cs
Content.Client/Salvage/RestrictedRangeSystem.cs [new file with mode: 0644]
Content.Client/Weather/WeatherOverlay.cs [deleted file]
Content.Client/Weather/WeatherSystem.cs
Content.Server/Gateway/Components/GatewayComponent.cs
Content.Server/Gateway/Components/GatewayDestinationComponent.cs [deleted file]
Content.Server/Gateway/Components/GatewayGeneratorComponent.cs [new file with mode: 0644]
Content.Server/Gateway/Components/GatewayGeneratorDestinationComponent.cs [new file with mode: 0644]
Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs [new file with mode: 0644]
Content.Server/Gateway/Systems/GatewaySystem.cs
Content.Server/Maps/PlanetCommand.cs
Content.Server/Movement/Systems/BoundarySystem.cs [new file with mode: 0644]
Content.Server/Parallax/BiomeSystem.Commands.cs
Content.Server/Parallax/BiomeSystem.cs
Content.Server/Procedural/DungeonSystem.cs
Content.Server/Salvage/RestrictedRangeSystem.cs [new file with mode: 0644]
Content.Server/Salvage/SpawnSalvageMissionJob.cs
Content.Shared/Gateway/GatewayUi.cs
Content.Shared/Gateway/SharedGatewayGeneratorSystem.cs [new file with mode: 0644]
Content.Shared/Movement/Components/BoundaryComponent.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/BiomeComponent.cs
Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs
Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs
Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs
Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs
Content.Shared/Salvage/RestrictedRangeComponent.cs [new file with mode: 0644]
Content.Shared/Salvage/SharedRestrictedRangeSystem.cs [new file with mode: 0644]
Content.Shared/Teleportation/Components/LinkedEntityComponent.cs
Content.Shared/Teleportation/Components/PortalComponent.cs
Content.Shared/Teleportation/Systems/LinkedEntitySystem.cs
Content.Shared/Teleportation/Systems/SharedPortalSystem.cs
Resources/Locale/en-US/gateway/gateway.ftl
Resources/Prototypes/Entities/Stations/base.yml
Resources/Prototypes/Entities/Stations/nanotrasen.yml
Resources/Prototypes/Entities/Structures/Machines/gateway.yml
Resources/Prototypes/Procedural/biome_markers.yml
Resources/Prototypes/Procedural/biome_ore_templates.yml
Resources/Prototypes/Procedural/biome_templates.yml
Resources/Prototypes/Procedural/salvage_loot.yml
Resources/Prototypes/Shaders/shaders.yml
Resources/Textures/Shaders/world_gradient_circle.swsl [new file with mode: 0644]
Resources/clientCommandPerms.yml

index 78e0060e9c7980ac5136554ce2ea372b5ff5192f..fdb3cdbc0109bcaa1a428e40c6de9e4b77e5aaca 100644 (file)
@@ -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));
index 49e6bb679b6fcb1644def247560c04ff61851f82..7650850a70cbbcc935b8ef29831c6e12ea558c6b 100644 (file)
@@ -3,11 +3,26 @@
                       Title="{Loc 'gateway-window-title'}"
                       MinSize="800 360">
     <BoxContainer Orientation="Vertical">
-           <BoxContainer Orientation="Horizontal">
-               <Label Name="NextCloseLabel"
-                      Text="{Loc 'gateway-window-portal-closing'}"
-                      Margin="5"></Label>
-               <ProgressBar Name="NextCloseBar"
+        <BoxContainer Orientation="Horizontal">
+            <!-- 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"
index f2219084321b20210a7b88a5dafabd189eda15b7..889dd6e175905384356d9e1a763f35ae77826f6b 100644 (file)
@@ -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 List<GatewayDestinationData> _destinations = new();
+
+    public readonly NetEntity Owner;
+
     private NetEntity? _current;
-    private TimeSpan _nextClose;
-    private TimeSpan _lastOpen;
-    private List<Label> _readyLabels = default!;
-    private List<Button> _openButtons = default!;
+    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;
 
-    public GatewayWindow()
+    /// <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),
+            };
+
+            // HOW DO I ALIGN THESE GOODER
+            var nameLabel = new RichTextLabel()
+            {
+                VerticalAlignment = VAlignment.Center,
+                SetWidth = 156f,
             };
 
-            box.AddChild(new Label()
+            nameLabel.SetMessage(name);
+            box.AddChild(nameLabel);
+            // Buffer
+            box.AddChild(new Control()
             {
-                Text = name
+                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;
-                NextCloseText.Text = "00:00";
+                if (_isUnlockPending)
+                {
+                    dirtyState = true;
+                    _isUnlockPending = false;
+                }
+
+                NextUnlockBar.Value = 1f;
+                NextUnlockText.Text = "00:00";
             }
             else
             {
-                var openTime = _nextClose - _lastOpen;
-                NextCloseBar.Value = 1f - (float) (remaining / openTime);
-                NextCloseText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
+                NextUnlockBar.Value = 1f - (float) (remaining.TotalSeconds / _unlockTime.TotalSeconds);
+                NextUnlockText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
             }
         }
 
-        for (var i = 0; i < _destinations.Count; i++)
+        // if its not going to close then show it as empty
+        if (_current == null || _cooldown == TimeSpan.Zero)
         {
-            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;
+            NextReadyBar.Value = 1f;
+            NextCloseText.Text = "00:00";
         }
-    }
+        else
+        {
+            var remaining = _nextReady - now;
+            if (remaining < TimeSpan.Zero)
+            {
+                if (_isCooldownPending)
+                {
+                    dirtyState = true;
+                    _isCooldownPending = false;
+                }
 
-    private string ReadyText(TimeSpan now, TimeSpan nextReady, bool busy)
-    {
-        if (busy)
-            return Loc.GetString("gateway-window-already-active");
+                NextReadyBar.Value = 1f;
+                NextCloseText.Text = "00:00";
+            }
+            else
+            {
+                NextReadyBar.Value = 1f - (float) (remaining.TotalSeconds / _cooldown.TotalSeconds);
+                NextCloseText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
+            }
+        }
 
-        if (now < nextReady)
+        if (dirtyState && _lastState != null)
         {
-            var time = nextReady - now;
-            return Loc.GetString("gateway-window-ready-in", ("time", $"{time.Minutes:00}:{time.Seconds:00}"));
+            // Refresh UI buttons.
+            UpdateState(_lastState);
         }
-
-        return Loc.GetString("gateway-window-ready");
     }
 }
diff --git a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs
new file mode 100644 (file)
index 0000000..9581fec
--- /dev/null
@@ -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));
+    }
+}
diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs
new file mode 100644 (file)
index 0000000..10a605f
--- /dev/null
@@ -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);
+    }
+}
diff --git a/Content.Client/Overlays/StencilOverlay.cs b/Content.Client/Overlays/StencilOverlay.cs
new file mode 100644 (file)
index 0000000..e475dca
--- /dev/null
@@ -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);
+    }
+}
diff --git a/Content.Client/Overlays/StencilOverlaySystem.cs b/Content.Client/Overlays/StencilOverlaySystem.cs
new file mode 100644 (file)
index 0000000..c8a9553
--- /dev/null
@@ -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>();
+    }
+}
diff --git a/Content.Client/Parallax/BiomeDebugOverlay.cs b/Content.Client/Parallax/BiomeDebugOverlay.cs
new file mode 100644 (file)
index 0000000..d9a1507
--- /dev/null
@@ -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());
+    }
+}
diff --git a/Content.Client/Parallax/Commands/ShowBiomeCommand.cs b/Content.Client/Parallax/Commands/ShowBiomeCommand.cs
new file mode 100644 (file)
index 0000000..2a628dd
--- /dev/null
@@ -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());
+        }
+    }
+}
index 8b96cbdc3da687879315e9a3fb8160cca30fbaad..721dc3a8c291783954f2ee6c6fb409bdecb30417 100644 (file)
@@ -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);
+            }
+        }
+    }
 }
diff --git a/Content.Client/Salvage/RestrictedRangeSystem.cs b/Content.Client/Salvage/RestrictedRangeSystem.cs
new file mode 100644 (file)
index 0000000..c81fcf1
--- /dev/null
@@ -0,0 +1,7 @@
+using Content.Shared.Salvage;
+
+namespace Content.Client.Salvage;
+
+public sealed class RestrictedRangeSystem : SharedRestrictedRangeSystem
+{
+}
diff --git a/Content.Client/Weather/WeatherOverlay.cs b/Content.Client/Weather/WeatherOverlay.cs
deleted file mode 100644 (file)
index 12af31b..0000000
+++ /dev/null
@@ -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);
-    }
-}
index e1742f479000484d0aeaff86bc90522ea716a4f7..888942363a91223f98d8e3be6136f44838fe3478 100644 (file)
@@ -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);
index 5145cb6b08b9afa066adf1255b0baf740f2ba039..aa3e3b4cb84c818fd719c5c9c64ea4591e76164f 100644 (file)
@@ -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();
-
-    /// <summary>
-    /// The time at which the portal will be closed.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
-    public TimeSpan NextClose;
+    public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
 
     /// <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;
 }
diff --git a/Content.Server/Gateway/Components/GatewayDestinationComponent.cs b/Content.Server/Gateway/Components/GatewayDestinationComponent.cs
deleted file mode 100644 (file)
index 41a4145..0000000
+++ /dev/null
@@ -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;
-}
diff --git a/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs b/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs
new file mode 100644 (file)
index 0000000..bbbebdb
--- /dev/null
@@ -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",
+    };
+}
+
diff --git a/Content.Server/Gateway/Components/GatewayGeneratorDestinationComponent.cs b/Content.Server/Gateway/Components/GatewayGeneratorDestinationComponent.cs
new file mode 100644 (file)
index 0000000..1bddff3
--- /dev/null
@@ -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;
+}
+
diff --git a/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs b/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs
new file mode 100644 (file)
index 0000000..cf81a5e
--- /dev/null
@@ -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);
+            }
+        }
+    }
+}
index ae00cd378c0756fc84928085eac4086fc8108382..34ca80eaf566fa9d0ae111c612508a5ffdfd0127 100644 (file)
@@ -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;
+        component.Enabled = value;
+        UpdateAllGateways();
+    }
 
-            ClosePortal(uid, comp);
-        }
+    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);
-        UpdateUserInterface(uid, comp);
-        UpdateAppearance(uid);
-        UpdateAppearance(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);
index 8bfd2b9de798d86a756c04b9c3104acce5ad0e48..718b4a64d919ba589a16721c944467f255242f59 100644 (file)
@@ -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 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);
+        var mapUid = _mapManager.GetMapEntityId(mapId);
+        biomeSystem.EnsurePlanet(mapUid, biomeTemplate);
 
-        _entManager.EnsureComponent<MapGridComponent>(mapUid);
         shell.WriteLine(Loc.GetString("cmd-planet-success", ("mapId", mapId)));
     }
 
diff --git a/Content.Server/Movement/Systems/BoundarySystem.cs b/Content.Server/Movement/Systems/BoundarySystem.cs
new file mode 100644 (file)
index 0000000..a798f10
--- /dev/null
@@ -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);
+    }
+}
index 0a60a3f978c155cba3f756a9360d3b82cfb45a73..279258129242b33ba960d656e1bab52ddedff2c6 100644 (file)
@@ -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;
index c9017ac821e191a29b716cb8556d81b18de57900..2687c8a6908332e4054372ec2cdb6fa7bdec57e9 100644 (file)
@@ -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 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 (proto != layerProto.EntityMask)
+                        // 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);
+    }
 }
index 0fb343cdfcfaf493113024413624f6d981258af8..9e85d86bf1739e996562595edfd16324ec00480d 100644 (file)
@@ -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(
diff --git a/Content.Server/Salvage/RestrictedRangeSystem.cs b/Content.Server/Salvage/RestrictedRangeSystem.cs
new file mode 100644 (file)
index 0000000..bc8b693
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.Salvage;
+
+namespace Content.Server.Salvage;
+
+public sealed class RestrictedRangeSystem : SharedRestrictedRangeSystem
+{
+
+}
index 276f35a83669f4174884767ec2d3349924dda354..c1e48228790e35226a6564827467e6883948ee56 100644 (file)
@@ -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;
index 75518c2be8b8038f673768829ddac29799788555..248588c7798363f711294e49636e2643029620fc 100644 (file)
@@ -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>
+    /// Next time the destination generator unlocks another destination.
+    /// </summary>
+    public readonly TimeSpan NextUnlock;
 
     /// <summary>
-    /// Time the portal last opened at.
+    /// How long an unlock takes.
     /// </summary>
-    public readonly TimeSpan LastOpen;
+    public readonly TimeSpan UnlockTime;
 
-    public GatewayBoundUserInterfaceState(List<(NetEntity, string, TimeSpan, bool)> destinations,
-        NetEntity? current, TimeSpan nextClose, TimeSpan lastOpen)
+    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
 {
diff --git a/Content.Shared/Gateway/SharedGatewayGeneratorSystem.cs b/Content.Shared/Gateway/SharedGatewayGeneratorSystem.cs
new file mode 100644 (file)
index 0000000..3145b07
--- /dev/null
@@ -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;
+}
diff --git a/Content.Shared/Movement/Components/BoundaryComponent.cs b/Content.Shared/Movement/Components/BoundaryComponent.cs
new file mode 100644 (file)
index 0000000..3618b1d
--- /dev/null
@@ -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;
+}
index 0fe21821728856efbe90dc4f24e09f57a6875c74..498619a17dd73e0869c35b00a29954ca962f8f12 100644 (file)
@@ -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;
index b2f55f46edcb142abffae77bf02b05ddd54cf5db..683900c8ba2206f901ec246981d6993ce051b390 100644 (file)
@@ -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]
+    public int MinGroupSize = 1;
+
+    /// <summary>
+    /// Maximum entities to spawn in one group.
     /// </summary>
-    [DataField("groupCount")]
-    public int GroupCount = 1;
+    [DataField]
+    public int MaxGroupSize = 1;
 
     /// <inheritdoc />
     [DataField("size")]
index f92aa6881123af421932d97e6c3ec070355e3bb0..de2913bb09dc1f30a2aab083ba6860fbd8ca9b32 100644 (file)
@@ -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.
index e22f009760b86187a09228d33733d38b0681a553..a65251a0f6f771027b25adf1ac353618b77ae63b 100644 (file)
@@ -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;
     }
 }
index 600685cab5044f1691f6af74617fff7b00d06c54..2eda4b059c8f565a155150935e034597bea18e35 100644 (file)
@@ -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();
 }
diff --git a/Content.Shared/Salvage/RestrictedRangeComponent.cs b/Content.Shared/Salvage/RestrictedRangeComponent.cs
new file mode 100644 (file)
index 0000000..d164fb2
--- /dev/null
@@ -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;
+}
diff --git a/Content.Shared/Salvage/SharedRestrictedRangeSystem.cs b/Content.Shared/Salvage/SharedRestrictedRangeSystem.cs
new file mode 100644 (file)
index 0000000..115a974
--- /dev/null
@@ -0,0 +1,6 @@
+namespace Content.Shared.Salvage;
+
+public abstract class SharedRestrictedRangeSystem : EntitySystem
+{
+
+}
index f9ad509d1fe3c821c76ff7a855d5eee719b71849..104136fb7bd4f2339ca195b96ad9bcb431aaf869 100644 (file)
@@ -5,7 +5,7 @@ using Robust.Shared.Serialization;
 namespace Content.Shared.Teleportation.Components;
 
 /// <summary>
-///     Represents an entity which is linked to other entities (perhaps portals), and which can be walked through/
+///     Represents an entity which is linked to other entities (perhaps portals), and which can be walked through /
 ///     thrown into to teleport an entity.
 /// </summary>
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
index eb81070a1e2d7ff1102038651bbe679e86e31077..26c0328e4759560755b38a6925fc463b2671666c 100644 (file)
@@ -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;
 }
index ed979754527a9a88ed855ba3f441d05a2c44fa6a..bf2d087c7619600187099f052d1eab6827775c56 100644 (file)
@@ -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.
index e614a2c040ff01cf4ebfa95b5603bb92b07acc56..13deb869bc70f0aa6478f508e12741db2d73222b 100644 (file)
@@ -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,7 +142,8 @@ public abstract class SharedPortalSystem : EntitySystem
             return;
 
         // no linked entity--teleport randomly
-        TeleportRandomly(uid, subject, component);
+        if (component.RandomTeleport)
+            TeleportRandomly(uid, subject, component);
     }
 
     private void OnEndCollide(EntityUid uid, PortalComponent component, ref EndCollideEvent args)
index bebc82b60f6e5d1db1fed11a27306c70b6fc2304..675d0fb873a4f0f7b9b7ce61292403404af4b4bb 100644 (file)
@@ -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
index ae84ceac65bb143bff30994b178c356ec6b7fcc5..856a37029d801ad49e6f237d217826f3d38cad1c 100644 (file)
   components:
     - type: StationArrivals
 
+- type: entity
+  id: BaseStationGateway
+  abstract: true
+  components:
+    - type: GatewayGenerator
+
+
 - type: entity
   id: BaseStationShuttles
   abstract: true
index 701db325882f67307ff429135b1dbdc3e2e94951..b83c6d0a526d0bc351b6761a9dbb2f259d71be79 100644 (file)
@@ -14,6 +14,7 @@
     - BaseStationJobsSpawning
     - BaseStationRecords
     - BaseStationArrivals
+    - BaseStationGateway
     - BaseStationShuttles
     - BaseStationCentcomm
     - BaseStationEvacuation
index a729b00d1468fc2aaac5c5f4ca6d9000d550ab70..b6ad9db356f3fc73eb54177707aebc8d2ee3eea8 100644 (file)
   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
index f1bc60a069454840850f9b0f6b16758531880eac..e70b46e6c5bf626e6b4ba13297afcac10c62cd12 100644 (file)
@@ -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
index c9830f2c17e74256e22a247b4391558088537f52..6a77171ca79e427537b1b710b14d4af9fa999a3c 100644 (file)
 # 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
index b2914b9ba6b3e35dc51c1c7b79379a8f8de2640a..38c7c7881b7245b961278f8232a3f3d8854521e7 100644 (file)
@@ -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:
         fractalType: FBm
         octaves: 5
         lacunarity: 2
-        gain: 1
         cellularDistanceFunction: Euclidean
         cellularReturnType: Distance2
       decals:
         lacunarity: 2
         fractalType: FBm
         octaves: 5
-        gain: 1
         cellularDistanceFunction: Euclidean
         cellularReturnType: Distance2
       entities:
         lacunarity: 2
         fractalType: FBm
         octaves: 5
-        gain: 1
         cellularDistanceFunction: Euclidean
         cellularReturnType: Distance2
 
         lacunarity: 2
         fractalType: FBm
         octaves: 5
-        gain: 1
         cellularDistanceFunction: Euclidean
         cellularReturnType: Distance2
       entities:
         fractalType: FBm
         octaves: 5
         lacunarity: 2
-        gain: 1
         cellularDistanceFunction: Euclidean
         cellularReturnType: Distance2
       decals:
         lacunarity: 2
         fractalType: FBm
         octaves: 5
-        gain: 1
         cellularDistanceFunction: Euclidean
         cellularReturnType: Distance2
       entities:
         lacunarity: 2
         fractalType: FBm
         octaves: 5
-        gain: 1
         cellularDistanceFunction: Euclidean
         cellularReturnType: Distance2
     - !type:BiomeDummyLayer
         fractalType: Ridged
         octaves: 1
         frequency: 0.1
-        gain: 0
+        gain: 0.5
       allowedTiles:
         - FloorAsteroidSand
       entities:
index 54870943b2f0fce413203618c6dfe201d62beb15..523fab44252a5d67df8f05a5bceec3e96981c865 100644 (file)
   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
   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
   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
index 7e165e47c1d16e381b6604a7bd5a3321fc6762fb..700ad325363f31632641618f030a0e56184a3a19 100644 (file)
@@ -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
@@ -76,7 +81,7 @@
   id: PaperStamp
   kind: source
   path: "/Textures/Shaders/paperstamp.swsl"
-  
+
 # Simple horizontal cut
 - type: shader
   id: HorizontalCut
diff --git a/Resources/Textures/Shaders/world_gradient_circle.swsl b/Resources/Textures/Shaders/world_gradient_circle.swsl
new file mode 100644 (file)
index 0000000..8b0be51
--- /dev/null
@@ -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);
+       }
+}
index 1ea9b6a716f2f99d324bc05e9f32ba8d7907a1a5..6f0c5b5e3af9cf22479677b9afb56fba09a81ce5 100644 (file)
@@ -57,6 +57,7 @@
     - toggledecals
     - nodevis
     - nodevisfilter
+    - showbiome
     - net_draw_interp
     - showmeleespread
     - showgunspread