Gateway destinations (#21040)

* Gateway generation

* Gateway stuff

* gatewehs

* mercenaries

* play area

* Range fixes and tweaks

* weh

* Gateway UI polish

* Lots of fixes

* Knock some items off

* Fix dungeon spawning

Realistically we should probably be using a salvage job.

* wahwah

* wehvs

* expression

* weh

* eee

* a

* a

* WEH

* frfr

* Gatwey

* Fix gateway windows

* Fix gateway windows

* a

* a

* Better layer masking

* a

* a

* Noise fixes

* a

* Fix fractal calculations

* a

* More fixes

* Fixes

* Add layers back in

* Fixes

* namespaces and ftl

* Other TODO

* Fix distance

* Cleanup

* Fix test
This commit is contained in:
metalgearsloth
2023-11-15 13:23:40 +11:00
committed by GitHub
parent 67a3c3a6a3
commit 816ee2e1ab
51 changed files with 1562 additions and 959 deletions

View 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));

View File

@@ -4,10 +4,25 @@
MinSize="800 360">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Name="NextCloseLabel"
Text="{Loc 'gateway-window-portal-closing'}"
Margin="5"></Label>
<ProgressBar Name="NextCloseBar"
<!-- This is wide as shit but makes it consistent with the cooldown label +
handles localisations a bit better -->
<Label Name="NextUnlockLabel"
Text="{Loc 'gateway-window-portal-unlock'}"
Margin="5"
SetWidth="128"/>
<ProgressBar Name="NextUnlockBar"
HorizontalExpand="True"
MinValue="0"
MaxValue="1"
SetHeight="25"/>
<Label Name="NextUnlockText" Text="0" Margin="5"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Name="NextReadyLabel"
Text="{Loc 'gateway-window-portal-cooldown'}"
Margin="5"
SetWidth="128"/>
<ProgressBar Name="NextReadyBar"
HorizontalExpand="True"
MinValue="0"
MaxValue="1"

View 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 NetEntity? _current;
private TimeSpan _nextClose;
private TimeSpan _lastOpen;
private List<Label> _readyLabels = default!;
private List<Button> _openButtons = default!;
private List<GatewayDestinationData> _destinations = new();
public GatewayWindow()
public readonly NetEntity Owner;
private NetEntity? _current;
private TimeSpan _nextReady;
private TimeSpan _cooldown;
private TimeSpan _unlockTime;
private TimeSpan _nextUnlock;
/// <summary>
/// Re-apply the state if the timer has elapsed.
/// </summary>
private GatewayBoundUserInterfaceState? _lastState;
/// <summary>
/// Are we currently waiting on an unlock timer.
/// </summary>
private bool _isUnlockPending = true;
/// <summary>
/// Are we currently waiting on a cooldown timer.
/// </summary>
private bool _isCooldownPending = true;
public GatewayWindow(NetEntity netEntity)
{
RobustXamlLoader.Load(this);
var dependencies = IoCManager.Instance!;
_timing = dependencies.Resolve<IGameTiming>();
Owner = netEntity;
NextUnlockBar.ForegroundStyleBoxOverride = new StyleBoxFlat(Color.FromHex("#C74EBD"));
}
public void UpdateState(GatewayBoundUserInterfaceState state)
{
_destinations = state.Destinations;
_current = state.Current;
_nextClose = state.NextClose;
_lastOpen = state.LastOpen;
_nextReady = state.NextReady;
_cooldown = state.Cooldown;
_unlockTime = state.UnlockTime;
_nextUnlock = state.NextUnlock;
_isUnlockPending = _nextUnlock >= _timing.CurTime;
_isCooldownPending = _nextReady >= _timing.CurTime;
Container.DisposeAllChildren();
_readyLabels = new List<Label>(_destinations.Count);
_openButtons = new List<Button>(_destinations.Count);
if (_destinations.Count == 0)
{
@@ -63,38 +89,63 @@ public sealed partial class GatewayWindow : FancyWindow,
}
var now = _timing.CurTime;
foreach (var dest in _destinations)
{
var ent = dest.Item1;
var name = dest.Item2;
var nextReady = dest.Item3;
var busy = dest.Item4;
var ent = dest.Entity;
var name = dest.Name;
var locked = dest.Locked && _nextUnlock > _timing.CurTime;
var box = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Margin = new Thickness(5f, 5f)
Margin = new Thickness(5f, 5f),
};
box.AddChild(new Label()
// HOW DO I ALIGN THESE GOODER
var nameLabel = new RichTextLabel()
{
Text = name
VerticalAlignment = VAlignment.Center,
SetWidth = 156f,
};
nameLabel.SetMessage(name);
box.AddChild(nameLabel);
// Buffer
box.AddChild(new Control()
{
HorizontalExpand = true,
});
var readyLabel = new Label
bool Pressable() => ent == _current || ent == Owner;
var buttonStripe = new StripeBack()
{
Text = ReadyText(now, nextReady, busy),
Margin = new Thickness(10f, 0f, 0f, 0f)
Visible = locked,
HorizontalExpand = true,
VerticalExpand = true,
Margin = new Thickness(10f, 0f, 0f, 0f),
Children =
{
new Label()
{
Text = Loc.GetString("gateway-window-locked"),
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
}
}
};
_readyLabels.Add(readyLabel);
box.AddChild(readyLabel);
var openButton = new Button()
{
Text = Loc.GetString("gateway-window-open-portal"),
Pressed = ent == _current,
Pressed = Pressable(),
ToggleMode = true,
Disabled = _current != null || busy || now < nextReady
Disabled = now < _nextReady || Pressable(),
HorizontalAlignment = HAlignment.Right,
Margin = new Thickness(10f, 0f, 0f, 0f),
Visible = !locked,
SetHeight = 32f,
};
openButton.OnPressed += args =>
@@ -102,21 +153,22 @@ public sealed partial class GatewayWindow : FancyWindow,
OpenPortal?.Invoke(ent);
};
if (ent == _current)
if (Pressable())
{
openButton.AddStyleClass(StyleBase.ButtonCaution);
}
_openButtons.Add(openButton);
box.AddChild(new BoxContainer()
var buttonContainer = new BoxContainer()
{
HorizontalExpand = true,
Align = BoxContainer.AlignMode.End,
Children =
{
openButton
}
});
buttonStripe,
openButton,
},
SetSize = new Vector2(128f, 40f),
};
box.AddChild(buttonContainer);
Container.AddChild(new PanelContainer()
{
@@ -128,6 +180,8 @@ public sealed partial class GatewayWindow : FancyWindow,
}
});
}
_lastState = state;
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -135,50 +189,66 @@ public sealed partial class GatewayWindow : FancyWindow,
base.FrameUpdate(args);
var now = _timing.CurTime;
var dirtyState = false;
// if its not going to close then show it as empty
if (_current == null)
if (_nextUnlock == TimeSpan.Zero)
{
NextCloseBar.Value = 0f;
NextCloseText.Text = "00:00";
NextUnlockBar.Value = 1f;
NextUnlockText.Text = "00:00";
}
else
{
var remaining = _nextClose - _timing.CurTime;
var remaining = _nextUnlock - now;
if (remaining < TimeSpan.Zero)
{
NextCloseBar.Value = 1f;
if (_isUnlockPending)
{
dirtyState = true;
_isUnlockPending = false;
}
NextUnlockBar.Value = 1f;
NextUnlockText.Text = "00:00";
}
else
{
NextUnlockBar.Value = 1f - (float) (remaining.TotalSeconds / _unlockTime.TotalSeconds);
NextUnlockText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
}
}
// if its not going to close then show it as empty
if (_current == null || _cooldown == TimeSpan.Zero)
{
NextReadyBar.Value = 1f;
NextCloseText.Text = "00:00";
}
else
{
var openTime = _nextClose - _lastOpen;
NextCloseBar.Value = 1f - (float) (remaining / openTime);
var remaining = _nextReady - now;
if (remaining < TimeSpan.Zero)
{
if (_isCooldownPending)
{
dirtyState = true;
_isCooldownPending = false;
}
NextReadyBar.Value = 1f;
NextCloseText.Text = "00:00";
}
else
{
NextReadyBar.Value = 1f - (float) (remaining.TotalSeconds / _cooldown.TotalSeconds);
NextCloseText.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
}
}
for (var i = 0; i < _destinations.Count; i++)
if (dirtyState && _lastState != null)
{
var dest = _destinations[i];
var nextReady = dest.Item3;
var busy = dest.Item4;
_readyLabels[i].Text = ReadyText(now, nextReady, busy);
_openButtons[i].Disabled = _current != null || busy || now < nextReady;
// Refresh UI buttons.
UpdateState(_lastState);
}
}
private string ReadyText(TimeSpan now, TimeSpan nextReady, bool busy)
{
if (busy)
return Loc.GetString("gateway-window-already-active");
if (now < nextReady)
{
var time = nextReady - now;
return Loc.GetString("gateway-window-ready-in", ("time", $"{time.Minutes:00}:{time.Seconds:00}"));
}
return Loc.GetString("gateway-window-ready");
}
}

View File

@@ -0,0 +1,57 @@
using System.Numerics;
using Content.Shared.Salvage;
using Robust.Client.Graphics;
using Robust.Shared.Utility;
namespace Content.Client.Overlays;
public sealed partial class StencilOverlay
{
private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3 invMatrix)
{
var worldHandle = args.WorldHandle;
var renderScale = args.Viewport.RenderScale.X;
// TODO: This won't handle non-standard zooms so uhh yeah, not sure how to structure it on the shader side.
var zoom = args.Viewport.Eye?.Zoom ?? Vector2.One;
var length = zoom.X;
var bufferRange = MathF.Min(10f, rangeComp.Range);
var pixelCenter = invMatrix.Transform(rangeComp.Origin);
// Something something offset?
var vertical = args.Viewport.Size.Y;
var pixelMaxRange = rangeComp.Range * renderScale / length * EyeManager.PixelsPerMeter;
var pixelBufferRange = bufferRange * renderScale / length * EyeManager.PixelsPerMeter;
var pixelMinRange = pixelMaxRange - pixelBufferRange;
_shader.SetParameter("position", new Vector2(pixelCenter.X, vertical - pixelCenter.Y));
_shader.SetParameter("maxRange", pixelMaxRange);
_shader.SetParameter("minRange", pixelMinRange);
_shader.SetParameter("bufferRange", pixelBufferRange);
_shader.SetParameter("gradient", 0.80f);
var worldAABB = args.WorldAABB;
var worldBounds = args.WorldBounds;
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
var localAABB = invMatrix.TransformBox(worldAABB);
// Cut out the irrelevant bits via stencil
// This is why we don't just use parallax; we might want specific tiles to get drawn over
// particularly for planet maps or stations.
worldHandle.RenderInRenderTarget(_blep!, () =>
{
worldHandle.UseShader(_shader);
worldHandle.DrawRect(localAABB, Color.White);
}, Color.Transparent);
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
var curTime = _timing.RealTime;
var sprite = _sprite.GetFrame(new SpriteSpecifier.Texture(new ResPath("/Textures/Parallaxes/noise.png")), curTime);
// Draw the rain
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, new Vector2(0.5f, 0f));
}
}

View File

@@ -0,0 +1,68 @@
using System.Numerics;
using Content.Shared.Weather;
using Robust.Client.Graphics;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
namespace Content.Client.Overlays;
public sealed partial class StencilOverlay
{
private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix)
{
var worldHandle = args.WorldHandle;
var mapId = args.MapId;
var worldAABB = args.WorldAABB;
var worldBounds = args.WorldBounds;
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
// Cut out the irrelevant bits via stencil
// This is why we don't just use parallax; we might want specific tiles to get drawn over
// particularly for planet maps or stations.
worldHandle.RenderInRenderTarget(_blep!, () =>
{
var bodyQuery = _entManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var weatherIgnoreQuery = _entManager.GetEntityQuery<IgnoreWeatherComponent>();
// idk if this is safe to cache in a field and clear sloth help
var grids = new List<Entity<MapGridComponent>>();
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref grids);
foreach (var grid in grids)
{
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
Matrix3.Multiply(in matrix, in invMatrix, out var matty);
worldHandle.SetTransform(matty);
foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
{
// Ignored tiles for stencil
if (_weather.CanWeatherAffect(grid, tile, weatherIgnoreQuery, bodyQuery))
{
continue;
}
var gridTile = new Box2(tile.GridIndices * grid.Comp.TileSize,
(tile.GridIndices + Vector2i.One) * grid.Comp.TileSize);
worldHandle.DrawRect(gridTile, Color.White);
}
}
}, Color.Transparent);
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
worldHandle.DrawTextureRect(_blep!.Texture, worldBounds);
var curTime = _timing.RealTime;
var sprite = _sprite.GetFrame(weatherProto.Sprite, curTime);
// Draw the rain
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha));
worldHandle.SetTransform(Matrix3.Identity);
worldHandle.UseShader(null);
}
}

View File

@@ -0,0 +1,77 @@
using Content.Client.Parallax;
using Content.Client.Weather;
using Content.Shared.Salvage;
using Content.Shared.Weather;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Overlays;
/// <summary>
/// Simple re-useable overlay with stencilled texture.
/// </summary>
public sealed partial class StencilOverlay : Overlay
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private readonly ParallaxSystem _parallax;
private readonly SharedTransformSystem _transform;
private readonly SpriteSystem _sprite;
private readonly WeatherSystem _weather;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
private IRenderTexture? _blep;
private readonly ShaderInstance _shader;
public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SpriteSystem sprite, WeatherSystem weather)
{
ZIndex = ParallaxSystem.ParallaxZIndex + 1;
_parallax = parallax;
_transform = transform;
_sprite = sprite;
_weather = weather;
IoCManager.InjectDependencies(this);
_shader = _protoManager.Index<ShaderPrototype>("WorldGradientCircle").InstanceUnique();
}
protected override void Draw(in OverlayDrawArgs args)
{
var mapUid = _mapManager.GetMapEntityId(args.MapId);
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
if (_blep?.Texture.Size != args.Viewport.Size)
{
_blep?.Dispose();
_blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "weather-stencil");
}
if (_entManager.TryGetComponent<WeatherComponent>(mapUid, out var comp))
{
foreach (var (proto, weather) in comp.Weather)
{
if (!_protoManager.TryIndex<WeatherPrototype>(proto, out var weatherProto))
continue;
var alpha = _weather.GetPercent(weather, mapUid);
DrawWeather(args, weatherProto, alpha, invMatrix);
}
}
if (_entManager.TryGetComponent<RestrictedRangeComponent>(mapUid, out var restrictedRangeComponent))
{
DrawRestrictedRange(args, restrictedRangeComponent, invMatrix);
}
args.WorldHandle.UseShader(null);
args.WorldHandle.SetTransform(Matrix3.Identity);
}
}

View File

@@ -0,0 +1,27 @@
using Content.Client.Parallax;
using Content.Client.Weather;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
namespace Content.Client.Overlays;
public sealed class StencilOverlaySystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly ParallaxSystem _parallax = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly WeatherSystem _weather = default!;
public override void Initialize()
{
base.Initialize();
_overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _sprite, _weather));
}
public override void Shutdown()
{
base.Shutdown();
_overlay.RemoveOverlay<StencilOverlay>();
}
}

View File

@@ -0,0 +1,88 @@
using System.Numerics;
using System.Text;
using Content.Shared.Parallax.Biomes;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Content.Client.Parallax;
public sealed class BiomeDebugOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
private BiomeSystem _biomes;
private SharedMapSystem _maps;
private Font _font;
public BiomeDebugOverlay()
{
IoCManager.InjectDependencies(this);
_biomes = _entManager.System<BiomeSystem>();
_maps = _entManager.System<SharedMapSystem>();
_font = new VectorFont(_cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 12);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
var mapUid = _mapManager.GetMapEntityId(args.MapId);
return _entManager.HasComponent<BiomeComponent>(mapUid);
}
protected override void Draw(in OverlayDrawArgs args)
{
var mouseScreenPos = _inputManager.MouseScreenPosition;
var mousePos = _eyeManager.ScreenToMap(mouseScreenPos);
if (mousePos.MapId == MapId.Nullspace || mousePos.MapId != args.MapId)
return;
var mapUid = _mapManager.GetMapEntityId(args.MapId);
if (!_entManager.TryGetComponent(mapUid, out BiomeComponent? biomeComp) || !_entManager.TryGetComponent(mapUid, out MapGridComponent? grid))
return;
var sb = new StringBuilder();
var nodePos = _maps.WorldToTile(mapUid, grid, mousePos.Position);
if (_biomes.TryGetEntity(nodePos, biomeComp, grid, out var ent))
{
var text = $"Entity: {ent}";
sb.AppendLine(text);
}
if (_biomes.TryGetDecals(nodePos, biomeComp.Layers, biomeComp.Seed, grid, out var decals))
{
var text = $"Decals: {decals.Count}";
sb.AppendLine(text);
foreach (var decal in decals)
{
var decalText = $"- {decal.ID}";
sb.AppendLine(decalText);
}
}
if (_biomes.TryGetBiomeTile(nodePos, biomeComp.Layers, biomeComp.Seed, grid, out var tile))
{
var tileText = $"Tile: {_tileDefManager[tile.Value.TypeId].ID}";
sb.AppendLine(tileText);
}
args.ScreenHandle.DrawString(_font, mouseScreenPos.Position + new Vector2(0f, 32f), sb.ToString());
}
}

View File

@@ -0,0 +1,22 @@
using Robust.Client.Graphics;
using Robust.Shared.Console;
namespace Content.Client.Parallax.Commands;
public sealed class ShowBiomeCommand : LocalizedCommands
{
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
public override string Command => "showbiome";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (_overlayMgr.HasOverlay<BiomeDebugOverlay>())
{
_overlayMgr.RemoveOverlay<BiomeDebugOverlay>();
}
else
{
_overlayMgr.AddOverlay(new BiomeDebugOverlay());
}
}
}

View 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);
}
}
}
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Salvage;
namespace Content.Client.Salvage;
public sealed class RestrictedRangeSystem : SharedRestrictedRangeSystem
{
}

View File

@@ -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);
}
}

View 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);

View 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();
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
/// <summary>
/// The time at which the portal will be closed.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextClose;
/// <summary>
/// The time at which the portal was last opened.
/// Only used for UI.
/// The time at which the portal can next be opened.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan LastOpen;
public TimeSpan NextReady;
}

View File

@@ -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;
}

View File

@@ -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",
};
}

View File

@@ -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;
}

View File

@@ -0,0 +1,234 @@
using System.Linq;
using System.Numerics;
using Content.Server.Gateway.Components;
using Content.Server.Parallax;
using Content.Server.Procedural;
using Content.Shared.Dataset;
using Content.Shared.Movement.Components;
using Content.Shared.Parallax.Biomes;
using Content.Shared.Physics;
using Content.Shared.Procedural;
using Content.Shared.Salvage;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Gateway.Systems;
/// <summary>
/// Generates gateway destinations regularly and indefinitely that can be chosen from.
/// </summary>
public sealed class GatewayGeneratorSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
[Dependency] private readonly BiomeSystem _biome = default!;
[Dependency] private readonly DungeonSystem _dungeon = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly GatewaySystem _gateway = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedMapSystem _maps = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[ValidatePrototypeId<DatasetPrototype>]
private const string PlanetNames = "names_borer";
// TODO:
// Fix shader some more
// Show these in UI
// Use regular mobs for thingo.
// Use salvage mission params
// Add the funny song
// Put salvage params in the UI
// Re-use salvage config stuff for the RNG
// Have it in the UI like expeditions.
// Also add weather coz it's funny.
// Add songs (incl. the downloaded one) to the ambient music playlist for planet probably.
// Copy most of salvage mission spawner
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GatewayGeneratorComponent, EntityUnpausedEvent>(OnGeneratorUnpaused);
SubscribeLocalEvent<GatewayGeneratorComponent, MapInitEvent>(OnGeneratorMapInit);
SubscribeLocalEvent<GatewayGeneratorComponent, ComponentShutdown>(OnGeneratorShutdown);
SubscribeLocalEvent<GatewayGeneratorDestinationComponent, AttemptGatewayOpenEvent>(OnGeneratorAttemptOpen);
SubscribeLocalEvent<GatewayGeneratorDestinationComponent, GatewayOpenEvent>(OnGeneratorOpen);
}
private void OnGeneratorShutdown(EntityUid uid, GatewayGeneratorComponent component, ComponentShutdown args)
{
foreach (var genUid in component.Generated)
{
if (Deleted(genUid))
continue;
QueueDel(genUid);
}
}
private void OnGeneratorUnpaused(Entity<GatewayGeneratorComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUnlock += args.PausedTime;
}
private void OnGeneratorMapInit(EntityUid uid, GatewayGeneratorComponent generator, MapInitEvent args)
{
for (var i = 0; i < 3; i++)
{
GenerateDestination(uid, generator);
}
Dirty(uid, generator);
}
private void GenerateDestination(EntityUid uid, GatewayGeneratorComponent? generator = null)
{
if (!Resolve(uid, ref generator))
return;
var tileDef = _tileDefManager["FloorSteel"];
const int MaxOffset = 256;
var tiles = new List<(Vector2i Index, Tile Tile)>();
var seed = _random.Next();
var random = new Random(seed);
var mapId = _mapManager.CreateMap();
var mapUid = _mapManager.GetMapEntityId(mapId);
var origin = new Vector2i(random.Next(-MaxOffset, MaxOffset), random.Next(-MaxOffset, MaxOffset));
var restriction = AddComp<RestrictedRangeComponent>(mapUid);
restriction.Origin = origin;
_biome.EnsurePlanet(mapUid, _protoManager.Index<BiomeTemplatePrototype>("Continental"), seed);
var grid = Comp<MapGridComponent>(mapUid);
for (var x = -2; x <= 2; x++)
{
for (var y = -2; y <= 2; y++)
{
tiles.Add((new Vector2i(x, y) + origin, new Tile(tileDef.TileId, variant: random.NextByte(tileDef.Variants))));
}
}
// Clear area nearby as a sort of landing pad.
_maps.SetTiles(mapUid, grid, tiles);
var gatewayName = SharedSalvageSystem.GetFTLName(_protoManager.Index<DatasetPrototype>(PlanetNames), seed);
_metadata.SetEntityName(mapUid, gatewayName);
var originCoords = new EntityCoordinates(mapUid, origin);
var genDest = AddComp<GatewayGeneratorDestinationComponent>(mapUid);
genDest.Origin = origin;
genDest.Seed = seed;
genDest.Generator = uid;
// Enclose the area
var boundaryUid = Spawn(null, originCoords);
var boundaryPhysics = AddComp<PhysicsComponent>(boundaryUid);
var cShape = new ChainShape();
// Don't need it to be a perfect circle, just need it to be loosely accurate.
cShape.CreateLoop(Vector2.Zero, restriction.Range + 1f, false, count: 4);
_fixtures.TryCreateFixture(
boundaryUid,
cShape,
"boundary",
collisionLayer: (int) (CollisionGroup.HighImpassable | CollisionGroup.Impassable | CollisionGroup.LowImpassable),
body: boundaryPhysics);
_physics.WakeBody(boundaryUid, body: boundaryPhysics);
AddComp<BoundaryComponent>(boundaryUid);
// Create the gateway.
var gatewayUid = SpawnAtPosition(generator.Proto, originCoords);
var gatewayComp = Comp<GatewayComponent>(gatewayUid);
_gateway.SetDestinationName(gatewayUid, FormattedMessage.FromMarkup($"[color=#D381C996]{gatewayName}[/color]"), gatewayComp);
_gateway.SetEnabled(gatewayUid, true, gatewayComp);
generator.Generated.Add(mapUid);
}
private void OnGeneratorAttemptOpen(Entity<GatewayGeneratorDestinationComponent> ent, ref AttemptGatewayOpenEvent args)
{
if (ent.Comp.Loaded || args.Cancelled)
return;
if (!TryComp(ent.Comp.Generator, out GatewayGeneratorComponent? generatorComp))
return;
if (generatorComp.NextUnlock + _metadata.GetPauseTime(ent.Owner) <= _timing.CurTime)
return;
args.Cancelled = true;
}
private void OnGeneratorOpen(Entity<GatewayGeneratorDestinationComponent> ent, ref GatewayOpenEvent args)
{
if (ent.Comp.Loaded)
return;
if (TryComp(ent.Comp.Generator, out GatewayGeneratorComponent? generatorComp))
{
generatorComp.NextUnlock = _timing.CurTime + generatorComp.UnlockCooldown;
_gateway.UpdateAllGateways();
// Generate another destination to keep them going.
GenerateDestination(ent.Comp.Generator);
}
if (!TryComp(args.MapUid, out MapGridComponent? grid))
return;
ent.Comp.Locked = false;
ent.Comp.Loaded = true;
// Do dungeon
var seed = ent.Comp.Seed;
var origin = ent.Comp.Origin;
var random = new Random(seed);
var dungeonDistance = random.Next(3, 6);
var dungeonRotation = _dungeon.GetDungeonRotation(seed);
var dungeonPosition = (origin + dungeonRotation.RotateVec(new Vector2i(0, dungeonDistance))).Floored();
_dungeon.GenerateDungeon(_protoManager.Index<DungeonConfigPrototype>("Experiment"), args.MapUid, grid, dungeonPosition, seed);
// TODO: Dungeon mobs + loot.
// Do markers on the map.
if (TryComp(ent.Owner, out BiomeComponent? biomeComp) && generatorComp != null)
{
// - Loot
var lootLayers = generatorComp.LootLayers.ToList();
for (var i = 0; i < generatorComp.LootLayerCount; i++)
{
var layerIdx = random.Next(lootLayers.Count);
var layer = lootLayers[layerIdx];
lootLayers.RemoveSwap(layerIdx);
_biome.AddMarkerLayer(biomeComp, layer.Id);
}
// - Mobs
var mobLayers = generatorComp.MobLayers.ToList();
for (var i = 0; i < generatorComp.MobLayerCount; i++)
{
var layerIdx = random.Next(mobLayers.Count);
var layer = mobLayers[layerIdx];
mobLayers.RemoveSwap(layerIdx);
_biome.AddMarkerLayer(biomeComp, layer.Id);
}
}
}
}

View 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;
ClosePortal(uid, comp);
component.Enabled = value;
UpdateAllGateways();
}
private void OnGatewayUnpaused(EntityUid uid, GatewayComponent component, ref EntityUnpausedEvent args)
{
component.NextReady += args.PausedTime;
}
private void OnStartup(EntityUid uid, GatewayComponent comp, ComponentStartup args)
{
// add existing destinations
var query = EntityQueryEnumerator<GatewayDestinationComponent>();
while (query.MoveNext(out var dest, out _))
{
comp.Destinations.Add(dest);
}
// no need to update ui since its just been created, just do portal
UpdateAppearance(uid);
}
private void OnGatewayOpenAttempt(EntityUid uid, GatewayComponent component, ref ActivatableUIOpenAttemptEvent args)
{
if (!component.Enabled || !component.Interactable)
args.Cancel();
}
private void UpdateUserInterface<T>(EntityUid uid, GatewayComponent comp, T args)
{
UpdateUserInterface(uid, comp);
}
private void UpdateUserInterface(EntityUid uid, GatewayComponent comp)
public void UpdateAllGateways()
{
var destinations = new List<(NetEntity, string, TimeSpan, bool)>();
foreach (var destUid in comp.Destinations)
var query = AllEntityQuery<GatewayComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var comp, out var xform))
{
var dest = Comp<GatewayDestinationComponent>(destUid);
if (!dest.Enabled)
UpdateUserInterface(uid, comp, xform);
}
}
private void UpdateUserInterface(EntityUid uid, GatewayComponent comp, TransformComponent? xform = null)
{
if (!Resolve(uid, ref xform))
return;
var destinations = new List<GatewayDestinationData>();
var query = AllEntityQuery<GatewayComponent, TransformComponent>();
var nextUnlock = TimeSpan.Zero;
var unlockTime = TimeSpan.Zero;
// Next unlock is based off of:
// - Our station's unlock timer (if we have a station)
// - If our map is a generated destination then use the generator that made it
if (TryComp(_stations.GetOwningStation(uid), out GatewayGeneratorComponent? generatorComp) ||
(TryComp(xform.MapUid, out GatewayGeneratorDestinationComponent? generatorDestination) &&
TryComp(generatorDestination.Generator, out generatorComp)))
{
nextUnlock = generatorComp.NextUnlock;
unlockTime = generatorComp.UnlockCooldown;
}
while (query.MoveNext(out var destUid, out var dest, out var destXform))
{
if (!dest.Enabled || destUid == uid)
continue;
destinations.Add((GetNetEntity(destUid), dest.Name, dest.NextReady, HasComp<PortalComponent>(destUid)));
// Show destination if either no destination comp on the map or it's ours.
TryComp<GatewayGeneratorDestinationComponent>(destXform.MapUid, out var gatewayDestination);
destinations.Add(new GatewayDestinationData()
{
Entity = GetNetEntity(destUid),
// Fallback to grid's ID if applicable.
Name = dest.Name.IsEmpty && destXform.GridUid != null ? FormattedMessage.FromUnformatted(MetaData(destXform.GridUid.Value).EntityName) : dest.Name ,
Portal = HasComp<PortalComponent>(destUid),
// If NextUnlock < CurTime it's unlocked, however
// we'll always send the client if it's locked
// It can just infer unlock times locally and not have to worry about it here.
Locked = gatewayDestination != null && gatewayDestination.Locked
});
}
_linkedEntity.GetLink(uid, out var current);
var state = new GatewayBoundUserInterfaceState(destinations, GetNetEntity(current), comp.NextClose, comp.LastOpen);
var state = new GatewayBoundUserInterfaceState(
destinations,
GetNetEntity(current),
comp.NextReady,
comp.Cooldown,
nextUnlock,
unlockTime
);
_ui.TrySetUiState(uid, GatewayUiKey.Key, state);
}
@@ -92,38 +142,58 @@ public sealed class GatewaySystem : EntitySystem
private void OnOpenPortal(EntityUid uid, GatewayComponent comp, GatewayOpenPortalMessage args)
{
if (args.Session.AttachedEntity == null)
if (args.Session.AttachedEntity == null || GetNetEntity(uid) == args.Destination ||
!comp.Enabled || !comp.Interactable)
return;
// if the gateway has an access reader check it before allowing opening
var user = args.Session.AttachedEntity.Value;
if (CheckAccess(user, uid))
if (CheckAccess(user, uid, comp))
return;
// can't link if portal is already open on either side, the destination is invalid or on cooldown
var desto = GetEntity(args.Destination);
if (HasComp<PortalComponent>(uid) ||
HasComp<PortalComponent>(desto) ||
!TryComp<GatewayDestinationComponent>(desto, out var dest) ||
// If it's already open / not enabled / we're not ready DENY.
if (!TryComp<GatewayComponent>(desto, out var dest) ||
!dest.Enabled ||
_timing.CurTime < dest.NextReady)
_timing.CurTime < _metadata.GetPauseTime(uid) + comp.NextReady)
{
return;
}
// TODO: admin log???
ClosePortal(uid, comp, false);
OpenPortal(uid, comp, desto, dest);
}
private void OpenPortal(EntityUid uid, GatewayComponent comp, EntityUid dest, GatewayDestinationComponent destComp)
private void OpenPortal(EntityUid uid, GatewayComponent comp, EntityUid dest, GatewayComponent destComp, TransformComponent? destXform = null)
{
_linkedEntity.TryLink(uid, dest);
EnsureComp<PortalComponent>(uid).CanTeleportToOtherMaps = true;
EnsureComp<PortalComponent>(dest).CanTeleportToOtherMaps = true;
if (!Resolve(dest, ref destXform) || destXform.MapUid == null)
return;
var ev = new AttemptGatewayOpenEvent(destXform.MapUid.Value, dest);
RaiseLocalEvent(destXform.MapUid.Value, ref ev);
if (ev.Cancelled)
return;
_linkedEntity.OneWayLink(uid, dest);
var sourcePortal = EnsureComp<PortalComponent>(uid);
var targetPortal = EnsureComp<PortalComponent>(dest);
sourcePortal.CanTeleportToOtherMaps = true;
targetPortal.CanTeleportToOtherMaps = true;
sourcePortal.RandomTeleport = false;
targetPortal.RandomTeleport = false;
var openEv = new GatewayOpenEvent(destXform.MapUid.Value, dest);
RaiseLocalEvent(destXform.MapUid.Value, ref openEv);
// for ui
comp.LastOpen = _timing.CurTime;
// close automatically after time is up
comp.NextClose = comp.LastOpen + destComp.OpenTime;
comp.NextReady = _timing.CurTime + comp.Cooldown;
_audio.PlayPvs(comp.OpenSound, uid);
_audio.PlayPvs(comp.OpenSound, dest);
@@ -133,7 +203,7 @@ public sealed class GatewaySystem : EntitySystem
UpdateAppearance(dest);
}
private void ClosePortal(EntityUid uid, GatewayComponent? comp = null)
private void ClosePortal(EntityUid uid, GatewayComponent? comp = null, bool update = true)
{
if (!Resolve(uid, ref comp))
return;
@@ -142,7 +212,7 @@ public sealed class GatewaySystem : EntitySystem
if (!_linkedEntity.GetLink(uid, out var dest))
return;
if (TryComp<GatewayDestinationComponent>(dest, out var destComp))
if (TryComp<GatewayComponent>(dest, out var destComp))
{
// portals closed, put it on cooldown and let it eventually be opened again
destComp.NextReady = _timing.CurTime + destComp.Cooldown;
@@ -153,46 +223,35 @@ public sealed class GatewaySystem : EntitySystem
_linkedEntity.TryUnlink(uid, dest.Value);
RemComp<PortalComponent>(dest.Value);
if (update)
{
UpdateUserInterface(uid, comp);
UpdateAppearance(uid);
UpdateAppearance(dest.Value);
}
}
private void OnDestinationStartup(EntityUid uid, GatewayDestinationComponent comp, ComponentStartup args)
private void OnDestinationStartup(EntityUid uid, GatewayComponent comp, ComponentStartup args)
{
var query = EntityQueryEnumerator<GatewayComponent>();
var query = AllEntityQuery<GatewayComponent>();
while (query.MoveNext(out var gatewayUid, out var gateway))
{
gateway.Destinations.Add(uid);
UpdateUserInterface(gatewayUid, gateway);
}
UpdateAppearance(uid);
}
private void OnDestinationShutdown(EntityUid uid, GatewayDestinationComponent comp, ComponentShutdown args)
private void OnDestinationShutdown(EntityUid uid, GatewayComponent comp, ComponentShutdown args)
{
var query = EntityQueryEnumerator<GatewayComponent>();
var query = AllEntityQuery<GatewayComponent>();
while (query.MoveNext(out var gatewayUid, out var gateway))
{
gateway.Destinations.Remove(uid);
UpdateUserInterface(gatewayUid, gateway);
}
}
private void OnDestinationGetVerbs(EntityUid uid, GatewayDestinationComponent comp, GetVerbsEvent<AlternativeVerb> args)
{
if (!comp.Closeable || !args.CanInteract || !args.CanAccess)
return;
// a portal is open so add verb to close it
args.Verbs.Add(new AlternativeVerb()
{
Act = () => TryClose(uid, args.User),
Text = Loc.GetString("gateway-close-portal")
});
}
private void TryClose(EntityUid uid, EntityUid user)
{
// portal already closed so cant close it
@@ -222,4 +281,31 @@ public sealed class GatewaySystem : EntitySystem
_audio.PlayPvs(comp.AccessDeniedSound, uid);
return true;
}
public void SetDestinationName(EntityUid gatewayUid, FormattedMessage gatewayName, GatewayComponent? gatewayComp = null)
{
if (!Resolve(gatewayUid, ref gatewayComp))
return;
gatewayComp.Name = gatewayName;
Dirty(gatewayUid, gatewayComp);
}
}
/// <summary>
/// Raised directed on the target map when a GatewayDestination is attempted to be opened.
/// </summary>
[ByRefEvent]
public record struct AttemptGatewayOpenEvent(EntityUid MapUid, EntityUid GatewayDestinationUid)
{
public readonly EntityUid MapUid = MapUid;
public readonly EntityUid GatewayDestinationUid = GatewayDestinationUid;
public bool Cancelled = false;
}
/// <summary>
/// Raised directed on the target map when a gateway is opened.
/// </summary>
[ByRefEvent]
public readonly record struct GatewayOpenEvent(EntityUid MapUid, EntityUid GatewayDestinationUid);

View 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 mapUid = _mapManager.GetMapEntityId(mapId);
biomeSystem.EnsurePlanet(mapUid, biomeTemplate);
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
gravity.Enabled = true;
gravity.Inherent = true;
_entManager.Dirty(gravity, metadata);
// Day lighting
// Daylight: #D8B059
// Midday: #E6CB8B
// Moonlight: #2b3143
// Lava: #A34931
var light = _entManager.EnsureComponent<MapLightComponent>(mapUid);
light.AmbientLightColor = Color.FromHex("#D8B059");
_entManager.Dirty(light, metadata);
// Atmos
var atmos = _entManager.EnsureComponent<MapAtmosphereComponent>(mapUid);
var moles = new float[Atmospherics.AdjustedNumberOfGases];
moles[(int) Gas.Oxygen] = 21.824779f;
moles[(int) Gas.Nitrogen] = 82.10312f;
var mixture = new GasMixture(2500)
{
Temperature = 293.15f,
Moles = moles,
};
_entManager.System<AtmosphereSystem>().SetMapAtmosphere(mapUid, false, mixture, atmos);
_entManager.EnsureComponent<MapGridComponent>(mapUid);
shell.WriteLine(Loc.GetString("cmd-planet-success", ("mapId", mapId)));
}

View File

@@ -0,0 +1,32 @@
using Content.Shared.Movement.Components;
using Robust.Shared.Physics.Events;
namespace Content.Server.Movement.Systems;
public sealed class BoundarySystem : EntitySystem
{
/*
* The real reason this even exists is because with out mover controller it's really easy to clip out of bounds on chain shapes.
*/
[Dependency] private readonly SharedTransformSystem _xform = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BoundaryComponent, StartCollideEvent>(OnBoundaryCollide);
}
private void OnBoundaryCollide(Entity<BoundaryComponent> ent, ref StartCollideEvent args)
{
var center = _xform.GetWorldPosition(ent.Owner);
var otherXform = Transform(args.OtherEntity);
var collisionPoint = _xform.GetWorldPosition(otherXform);
var offset = collisionPoint - center;
offset = offset.Normalized() * (offset.Length() - ent.Comp.Offset);
// If for whatever reason you want to yeet them to the other side.
// offset = new Angle(MathF.PI).RotateVec(offset);
_xform.SetWorldPosition(otherXform, center + offset);
}
}

View 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;

View 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 (proto != layerProto.EntityMask)
// If there's an existing entity and it doesn't match the mask then skip.
if (layerProto.EntityMask.Count > 0 &&
(proto == null ||
!layerProto.EntityMask.ContainsKey(proto)))
{
continue;
}
// If it's just a flat spawn then just check for anything blocking.
if (proto != null && layerProto.Prototype != null)
{
continue;
}
DebugTools.Assert(layerProto.EntityMask.Count == 0 || !string.IsNullOrEmpty(proto));
var chunkOrigin = SharedMapSystem.GetChunkIndices(node, ChunkSize) * ChunkSize;
if (!pending.TryGetValue(chunkOrigin, out var pendingMarkers))
@@ -482,9 +496,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
pendingMarkers[layer] = layerMarkers;
}
// Log.Info($"Added node at {actualNode} for chunk {chunkOrigin}");
layerMarkers.Add(node);
groupCount--;
groupSize--;
spawnSet.Add(node);
}
}
@@ -527,7 +541,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
// Load NOW!
LoadChunk(component, gridUid, grid, chunk, noise, tiles, xformQuery);
LoadChunk(component, gridUid, grid, chunk, seed, tiles, xformQuery);
}
}
@@ -539,7 +553,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
EntityUid gridUid,
MapGridComponent grid,
Vector2i chunk,
FastNoiseLite noise,
int seed,
List<(Vector2i, Tile)> tiles,
EntityQuery<TransformComponent> xformQuery)
{
@@ -551,7 +565,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
{
foreach (var (layer, nodes) in layers)
{
var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
foreach (var node in nodes)
{
@@ -559,15 +573,27 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
continue;
// Need to ensure the tile under it has loaded for anchoring.
if (TryGetBiomeTile(node, component.Layers, component.Noise, grid, out var tile))
if (TryGetBiomeTile(node, component.Layers, seed, grid, out var tile))
{
grid.SetTile(node, tile.Value);
_mapSystem.SetTile(gridUid, grid, node, tile.Value);
}
string? prototype;
if (TryGetEntity(node, component, grid, out var proto) &&
layerProto.EntityMask.TryGetValue(proto, out var maskedProto))
{
prototype = maskedProto;
}
else
{
prototype = layerProto.Prototype;
}
// If it is a ghost role then purge it
// TODO: This is *kind* of a bandaid but natural mobs spawns needs a lot more work.
// Ideally we'd just have ghost role and non-ghost role variants for some stuff.
var uid = EntityManager.CreateEntityUninitialized(layerProto.Prototype, grid.GridTileToLocal(node));
var uid = EntityManager.CreateEntityUninitialized(prototype, _mapSystem.GridTileToLocal(gridUid, grid, node));
RemComp<GhostTakeoverAvailableComponent>(uid);
RemComp<GhostRoleComponent>(uid);
EntityManager.InitializeAndStartEntity(uid);
@@ -585,22 +611,22 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
{
var indices = new Vector2i(x + chunk.X, y + chunk.Y);
// Pass in null so we don't try to get the tileref.
if (modified.Contains(indices))
continue;
// If there's existing data then don't overwrite it.
if (grid.TryGetTileRef(indices, out var tileRef) && !tileRef.Tile.IsEmpty)
if (_mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
continue;
// Pass in null so we don't try to get the tileref.
if (!TryGetBiomeTile(indices, component.Layers, noise, null, out var biomeTile) || biomeTile.Value == tileRef.Tile)
if (!TryGetBiomeTile(indices, component.Layers, seed, grid, out var biomeTile) || biomeTile.Value == tileRef.Tile)
continue;
tiles.Add((indices, biomeTile.Value));
}
}
grid.SetTiles(tiles);
_mapSystem.SetTiles(gridUid, grid, tiles);
tiles.Clear();
// Now do entities
@@ -617,14 +643,14 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
continue;
// Don't mess with anything that's potentially anchored.
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
if (anchored.MoveNext(out _) || !TryGetEntity(indices, component.Layers, noise, grid, out var entPrototype))
if (anchored.MoveNext(out _) || !TryGetEntity(indices, component, grid, out var entPrototype))
continue;
// TODO: Fix non-anchored ents spawning.
// Just track loaded chunks for now.
var ent = Spawn(entPrototype, grid.GridTileToLocal(indices));
var ent = Spawn(entPrototype, _mapSystem.GridTileToLocal(gridUid, grid, indices));
// At least for now unless we do lookups or smth, only work with anchoring.
if (xformQuery.TryGetComponent(ent, out var xform) && !xform.Anchored)
@@ -650,9 +676,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
continue;
// Don't mess with anything that's potentially anchored.
var anchored = grid.GetAnchoredEntitiesEnumerator(indices);
var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, noise, grid, out var decals))
if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, seed, grid, out var decals))
continue;
foreach (var decal in decals)
@@ -683,7 +709,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
/// <summary>
/// Handles all of the queued chunk unloads for a particular biome.
/// </summary>
private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, FastNoiseLite noise)
private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, int seed)
{
var active = _activeChunks[component];
List<(Vector2i, Tile)>? tiles = null;
@@ -695,14 +721,14 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
// Unload NOW!
tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
UnloadChunk(component, gridUid, grid, chunk, noise, tiles);
UnloadChunk(component, gridUid, grid, chunk, seed, tiles);
}
}
/// <summary>
/// Unloads a specific biome chunk.
/// </summary>
private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, FastNoiseLite noise, List<(Vector2i, Tile)> tiles)
private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, int seed, List<(Vector2i, Tile)> tiles)
{
// Reverse order to loading
component.ModifiedTiles.TryGetValue(chunk, out var modified);
@@ -735,7 +761,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
}
// It's moved
var entTile = grid.LocalToTile(xform.Coordinates);
var entTile = _mapSystem.LocalToTile(gridUid, grid, xform.Coordinates);
if (!xform.Anchored || entTile != tile)
{
@@ -775,8 +801,8 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
}
// If it's default data unload the tile.
if (!TryGetBiomeTile(indices, component.Layers, noise, null, out var biomeTile) ||
grid.TryGetTileRef(indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
if (!TryGetBiomeTile(indices, component.Layers, seed, null, out var biomeTile) ||
_mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
{
modified.Add(indices);
continue;
@@ -801,4 +827,51 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
}
#endregion
/// <summary>
/// Creates a simple planet setup for a map.
/// </summary>
public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, int? seed = null, MetaDataComponent? metadata = null)
{
if (!Resolve(mapUid, ref metadata))
return;
var biome = EnsureComp<BiomeComponent>(mapUid);
seed ??= _random.Next();
SetSeed(biome, seed.Value);
SetTemplate(biome, biomeTemplate);
Dirty(mapUid, biome, metadata);
var gravity = EnsureComp<GravityComponent>(mapUid);
gravity.Enabled = true;
gravity.Inherent = true;
Dirty(mapUid, gravity, metadata);
// Day lighting
// Daylight: #D8B059
// Midday: #E6CB8B
// Moonlight: #2b3143
// Lava: #A34931
var light = EnsureComp<MapLightComponent>(mapUid);
light.AmbientLightColor = Color.FromHex("#D8B059");
Dirty(mapUid, light, metadata);
// Atmos
var atmos = EnsureComp<MapAtmosphereComponent>(mapUid);
var moles = new float[Atmospherics.AdjustedNumberOfGases];
moles[(int) Gas.Oxygen] = 21.824779f;
moles[(int) Gas.Nitrogen] = 82.10312f;
var mixture = new GasMixture(2500)
{
Temperature = 293.15f,
Moles = moles,
};
_atmos.SetMapAtmosphere(mapUid, false, mixture, atmos);
EnsureComp<MapGridComponent>(mapUid);
}
}

View 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(

View File

@@ -0,0 +1,8 @@
using Content.Shared.Salvage;
namespace Content.Server.Salvage;
public sealed class RestrictedRangeSystem : SharedRestrictedRangeSystem
{
}

View 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;

View 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>
/// Time the portal last opened at.
/// Next time the destination generator unlocks another destination.
/// </summary>
public readonly TimeSpan LastOpen;
public readonly TimeSpan NextUnlock;
public GatewayBoundUserInterfaceState(List<(NetEntity, string, TimeSpan, bool)> destinations,
NetEntity? current, TimeSpan nextClose, TimeSpan lastOpen)
/// <summary>
/// How long an unlock takes.
/// </summary>
public readonly TimeSpan UnlockTime;
public GatewayBoundUserInterfaceState(List<GatewayDestinationData> destinations,
NetEntity? current, TimeSpan nextReady, TimeSpan cooldown, TimeSpan nextUnlock, TimeSpan unlockTime)
{
Destinations = destinations;
Current = current;
NextClose = nextClose;
LastOpen = lastOpen;
NextReady = nextReady;
Cooldown = cooldown;
NextUnlock = nextUnlock;
UnlockTime = unlockTime;
}
}
[Serializable, NetSerializable]
public record struct GatewayDestinationData
{
public NetEntity Entity;
public FormattedMessage Name;
/// <summary>
/// Is the portal currently open.
/// </summary>
public bool Portal;
/// <summary>
/// Is the map the gateway on locked or unlocked.
/// </summary>
public bool Locked;
}
[Serializable, NetSerializable]
public sealed class GatewayOpenPortalMessage : BoundUserInterfaceMessage
{

View File

@@ -0,0 +1,12 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Gateway;
/// <summary>
/// Sent from client to server upon taking a gateway destination.
/// </summary>
[Serializable, NetSerializable]
public sealed class GatewayDestinationMessage : EntityEventArgs
{
public int Index;
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Movement.Components;
/// <summary>
/// Represents a boundary that can bump someone back when touched.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class BoundaryComponent : Component
{
[DataField, AutoNetworkedField]
public float Offset = 2f;
}

View 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;

View 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("groupCount")]
public int GroupCount = 1;
[DataField]
public int MinGroupSize = 1;
/// <summary>
/// Maximum entities to spawn in one group.
/// </summary>
[DataField]
public int MaxGroupSize = 1;
/// <inheritdoc />
[DataField("size")]

View 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.

View 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;
}
}

View 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();
}

View File

@@ -0,0 +1,17 @@
using System.Numerics;
using Robust.Shared.GameStates;
namespace Content.Shared.Salvage;
/// <summary>
/// Restricts entities to the specified range on the attached map entity.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class RestrictedRangeComponent : Component
{
[DataField(required: true), AutoNetworkedField]
public float Range = 72f;
[DataField, AutoNetworkedField]
public Vector2 Origin;
}

View File

@@ -0,0 +1,6 @@
namespace Content.Shared.Salvage;
public abstract class SharedRestrictedRangeSystem : EntitySystem
{
}

View 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;
}

View 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.

View 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,6 +142,7 @@ public abstract class SharedPortalSystem : EntitySystem
return;
// no linked entity--teleport randomly
if (component.RandomTeleport)
TeleportRandomly(uid, subject, component);
}

View 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

View File

@@ -32,6 +32,13 @@
components:
- type: StationArrivals
- type: entity
id: BaseStationGateway
abstract: true
components:
- type: GatewayGenerator
- type: entity
id: BaseStationShuttles
abstract: true

View File

@@ -14,6 +14,7 @@
- BaseStationJobsSpawning
- BaseStationRecords
- BaseStationArrivals
- BaseStationGateway
- BaseStationShuttles
- BaseStationCentcomm
- BaseStationEvacuation

View File

@@ -50,20 +50,8 @@
components:
- type: ActivatableUI
key: enum.GatewayUiKey.Key
- type: ActivatableUIRequiresPower
- type: UserInterface
interfaces:
- key: enum.GatewayUiKey.Key
type: GatewayBoundUserInterface
- type: ApcPowerReceiver
powerLoad: 3000
- type: ExtensionCableReceiver
- type: Gateway
- type: entity
parent: BaseGateway
id: GatewayDestination
suffix: Destination
components:
- type: GatewayDestination
name: Unknown

View 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

View File

@@ -1,290 +1,103 @@
# Low value
- type: biomeMarkerLayer
id: OreTin
proto: WallRockTin
entityMask: WallRock
entityMask:
WallRock: WallRockTin
WallRockBasalt: WallRockBasaltTin
WallRockChromite: WallRockChromiteTin
WallRockSnow: WallRockSnowTin
maxCount: 30
groupCount: 10
minGroupSize: 10
maxGroupSize: 20
radius: 4
- type: biomeMarkerLayer
id: OreQuartz
proto: WallRockQuartz
entityMask: WallRock
entityMask:
WallRock: WallRockQuartz
WallRockBasalt: WallRockBasaltQuartz
WallRockChromite: WallRockChromiteQuartz
WallRockSnow: WallRockSnowQuartz
maxCount: 30
groupCount: 10
minGroupSize: 10
maxGroupSize: 20
radius: 4
# Medium value
# Gold
- type: biomeMarkerLayer
id: OreGold
proto: WallRockGold
entityMask: WallRock
entityMask:
WallRock: WallRockGold
WallRockBasalt: WallRockBasaltGold
WallRockChromite: WallRockChromiteGold
WallRockSnow: WallRockSnowGold
maxCount: 30
groupCount: 5
minGroupSize: 5
maxGroupSize: 10
radius: 4
# Silver
- type: biomeMarkerLayer
id: OreSilver
proto: WallRockSilver
entityMask: WallRock
entityMask:
WallRock: WallRockSilver
WallRockBasalt: WallRockBasaltSilver
WallRockChromite: WallRockChromiteSilver
WallRockSnow: WallRockSnowSilver
maxCount: 30
groupCount: 5
minGroupSize: 5
maxGroupSize: 10
radius: 4
# High value
# Plasma
- type: biomeMarkerLayer
id: OrePlasma
proto: WallRockPlasma
entityMask: WallRock
entityMask:
WallRock: WallRockPlasma
WallRockBasalt: WallRockBasaltPlasma
WallRockChromite: WallRockChromitePlasma
WallRockSnow: WallRockSnowPlasma
maxCount: 12
groupCount: 5
minGroupSize: 5
maxGroupSize: 10
radius: 4
# Uranium
- type: biomeMarkerLayer
id: OreUranium
proto: WallRockUranium
entityMask: WallRock
entityMask:
WallRock: WallRockUranium
WallRockBasalt: WallRockBasaltUranium
WallRockChromite: WallRockChromiteUranium
WallRockSnow: WallRockSnowUranium
maxCount: 12
groupCount: 5
minGroupSize: 5
maxGroupSize: 10
radius: 4
- type: biomeMarkerLayer
id: OreBananium
proto: WallRockBananium
entityMask: WallRock
entityMask:
WallRock: WallRockBananium
WallRockBasalt: WallRockBasaltBananium
WallRockChromite: WallRockChromiteBananium
WallRockSnow: WallRockSnowBananium
maxCount: 12
groupCount: 5
minGroupSize: 5
maxGroupSize: 10
radius: 4
# Artifact Fragment
- type: biomeMarkerLayer
id: OreArtifactFragment
proto: WallRockArtifactFragment
entityMask: WallRock
entityMask:
WallRock: WallRockArtifactFragment
WallRockBasalt: WallRockBasaltArtifactFragment
WallRockChromite: WallRockChromiteArtifactFragment
WallRockSnow: WallRockSnowArtifactFragment
maxCount: 6
groupCount: 1
radius: 4
# Basalt variant
# Low value
- type: biomeMarkerLayer
id: BasaltOreTin
proto: WallRockBasaltTin
entityMask: WallRockBasalt
maxCount: 30
groupCount: 10
radius: 4
- type: biomeMarkerLayer
id: BasaltOreQuartz
proto: WallRockBasaltQuartz
entityMask: WallRockBasalt
maxCount: 30
groupCount: 10
radius: 4
# Medium value
# Gold
- type: biomeMarkerLayer
id: BasaltOreGold
proto: WallRockBasaltGold
entityMask: WallRockBasalt
maxCount: 30
groupCount: 5
radius: 4
# Silver
- type: biomeMarkerLayer
id: BasaltOreSilver
proto: WallRockBasaltSilver
entityMask: WallRockBasalt
maxCount: 30
groupCount: 5
radius: 4
# High value
# Plasma
- type: biomeMarkerLayer
id: BasaltOrePlasma
proto: WallRockBasaltPlasma
entityMask: WallRockBasalt
maxCount: 12
groupCount: 5
radius: 4
# Uranium
- type: biomeMarkerLayer
id: BasaltOreUranium
proto: WallRockBasaltUranium
entityMask: WallRockBasalt
maxCount: 12
groupCount: 5
radius: 4
- type: biomeMarkerLayer
id: BasaltOreBananium
proto: WallRockBasaltBananium
entityMask: WallRockBasalt
maxCount: 12
groupCount: 5
radius: 4
# Artifact Fragment
- type: biomeMarkerLayer
id: BasaltOreArtifactFragment
proto: WallRockBasaltArtifactFragment
entityMask: WallRockBasalt
maxCount: 6
groupCount: 1
radius: 4
# Shadow basalt variant
# Low value
- type: biomeMarkerLayer
id: ChromiteOreTin
proto: WallRockChromiteTin
entityMask: WallRockChromite
maxCount: 30
groupCount: 10
radius: 4
- type: biomeMarkerLayer
id: ChromiteOreQuartz
proto: WallRockChromiteQuartz
entityMask: WallRockChromite
maxCount: 30
groupCount: 10
radius: 4
# Medium value
# Gold
- type: biomeMarkerLayer
id: ChromiteOreGold
proto: WallRockChromiteGold
entityMask: WallRockChromite
maxCount: 30
groupCount: 5
radius: 4
# Silver
- type: biomeMarkerLayer
id: ChromiteOreSilver
proto: WallRockChromiteSilver
entityMask: WallRockChromite
maxCount: 30
groupCount: 5
radius: 4
# High value
# Plasma
- type: biomeMarkerLayer
id: ChromiteOrePlasma
proto: WallRockChromitePlasma
entityMask: WallRockChromite
maxCount: 12
groupCount: 5
radius: 4
# Uranium
- type: biomeMarkerLayer
id: ChromiteOreUranium
proto: WallRockChromiteUranium
entityMask: WallRockChromite
maxCount: 12
groupCount: 5
radius: 4
- type: biomeMarkerLayer
id: ChromiteOreBananium
proto: WallRockChromiteBananium
entityMask: WallRockChromite
maxCount: 12
groupCount: 5
radius: 4
# Artifact Fragment
- type: biomeMarkerLayer
id: ChromiteOreArtifactFragment
proto: WallRockChromiteArtifactFragment
entityMask: WallRockChromite
maxCount: 6
groupCount: 1
radius: 4
# Snow variant
# Low value
- type: biomeMarkerLayer
id: SnowOreTin
proto: WallRockSnowTin
entityMask: WallRockSnow
maxCount: 30
groupCount: 10
radius: 4
- type: biomeMarkerLayer
id: SnowOreQuartz
proto: WallRockSnowQuartz
entityMask: WallRockSnow
maxCount: 30
groupCount: 10
radius: 4
# Medium value
# Gold
- type: biomeMarkerLayer
id: SnowOreGold
proto: WallRockSnowGold
entityMask: WallRockSnow
maxCount: 30
groupCount: 5
radius: 4
# Silver
- type: biomeMarkerLayer
id: SnowOreSilver
proto: WallRockSnowSilver
entityMask: WallRockSnow
maxCount: 30
groupCount: 5
radius: 4
# High value
# Plasma
- type: biomeMarkerLayer
id: SnowOrePlasma
proto: WallRockSnowPlasma
entityMask: WallRockSnow
maxCount: 12
groupCount: 5
radius: 4
# Uranium
- type: biomeMarkerLayer
id: SnowOreUranium
proto: WallRockSnowUranium
entityMask: WallRockSnow
maxCount: 12
groupCount: 5
radius: 4
- type: biomeMarkerLayer
id: SnowOreBananium
proto: WallRockSnowBananium
entityMask: WallRockSnow
maxCount: 12
groupCount: 5
radius: 4
# Artifact Fragment
- type: biomeMarkerLayer
id: SnowOreArtifactFragment
proto: WallRockSnowArtifactFragment
entityMask: WallRockSnow
maxCount: 6
groupCount: 1
minGroupSize: 1
maxGroupSize: 2
radius: 4

View 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:
@@ -129,7 +125,6 @@
fractalType: FBm
octaves: 5
lacunarity: 2
gain: 1
cellularDistanceFunction: Euclidean
cellularReturnType: Distance2
decals:
@@ -174,7 +169,6 @@
lacunarity: 2
fractalType: FBm
octaves: 5
gain: 1
cellularDistanceFunction: Euclidean
cellularReturnType: Distance2
entities:
@@ -229,7 +223,6 @@
lacunarity: 2
fractalType: FBm
octaves: 5
gain: 1
cellularDistanceFunction: Euclidean
cellularReturnType: Distance2
@@ -305,7 +298,6 @@
lacunarity: 2
fractalType: FBm
octaves: 5
gain: 1
cellularDistanceFunction: Euclidean
cellularReturnType: Distance2
entities:
@@ -370,7 +362,6 @@
fractalType: FBm
octaves: 5
lacunarity: 2
gain: 1
cellularDistanceFunction: Euclidean
cellularReturnType: Distance2
decals:
@@ -436,7 +427,6 @@
lacunarity: 2
fractalType: FBm
octaves: 5
gain: 1
cellularDistanceFunction: Euclidean
cellularReturnType: Distance2
entities:
@@ -452,7 +442,6 @@
lacunarity: 2
fractalType: FBm
octaves: 5
gain: 1
cellularDistanceFunction: Euclidean
cellularReturnType: Distance2
- !type:BiomeDummyLayer
@@ -586,7 +575,7 @@
fractalType: Ridged
octaves: 1
frequency: 0.1
gain: 0
gain: 0.5
allowedTiles:
- FloorAsteroidSand
entities:

View File

@@ -123,22 +123,14 @@
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OreTin
Grasslands: OreTin
Lava: BasaltOreTin
Snow: SnowOreTin
proto: OreTin
- type: salvageLoot
id: OreQuartz
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OreQuartz
Grasslands: OreQuartz
Lava: BasaltOreQuartz
Snow: SnowOreQuartz
proto: OreQuartz
# - Medium value
- type: salvageLoot
@@ -146,22 +138,14 @@
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OreGold
Grasslands: OreGold
Lava: BasaltOreGold
Snow: SnowOreGold
proto: OreGold
- type: salvageLoot
id: OreSilver
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OreSilver
Grasslands: OreSilver
Lava: BasaltOreSilver
Snow: SnowOreSilver
proto: OreSilver
# - High value
- type: salvageLoot
@@ -169,41 +153,25 @@
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OrePlasma
Grasslands: OrePlasma
Lava: BasaltOrePlasma
Snow: SnowOrePlasma
proto: OrePlasma
- type: salvageLoot
id: OreUranium
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OreUranium
Grasslands: OreUranium
Lava: BasaltOreUranium
Snow: SnowOreUranium
proto: OreUranium
- type: salvageLoot
id: OreBananium
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OreBananium
Grasslands: OreBananium
Lava: BasaltOreBananium
Snow: SnowOreBananium
proto: OreBananium
- type: salvageLoot
id: OreArtifactFragment
guaranteed: true
loots:
- !type:BiomeMarkerLoot
proto:
Caves: OreArtifactFragment
Grasslands: OreArtifactFragment
Lava: BasaltOreArtifactFragment
Snow: SnowOreArtifactFragment
proto: OreArtifactFragment

View 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

View File

@@ -0,0 +1,27 @@
// Has 2 circles, an inner one that is unaffected and an outer one.
light_mode unshaded;
const highp vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
// Position of the center in pixel terms.
uniform highp vec2 position;
uniform highp float maxRange;
uniform highp float minRange;
uniform highp float bufferRange;
uniform highp float gradient;
void fragment() {
highp float distance = length(FRAGCOORD.xy - position);
if (distance > maxRange) {
discard;
}
else if (distance < minRange) {
COLOR = color;
}
else {
highp float ratio = 1.0 - pow((distance - minRange) / bufferRange, gradient);
COLOR = vec4(color.x, color.y, color.z, ratio);
}
}

View File

@@ -57,6 +57,7 @@
- toggledecals
- nodevis
- nodevisfilter
- showbiome
- net_draw_interp
- showmeleespread
- showgunspread