Add planet lighting (#32522)
* Implements a Dynamic Lighting System on maps. * Edit: the night should be a little bit brighter and blue now. * Major edit: everything must be done on the client side now, with certain datafield replicated. Changes were outlined in the salvage to accommodate the new lighting system. * Edit: The offset is now serverside, this makes the time accurate in all situations. * Removing ununsed import * Minor tweaks * Tweak in time precision * Minor tweak + Unused import removed * Edit: apparently RealTime is better for what I'm looking for * Fix: Now the time is calculated correctly. * Minor tweaks * Adds condition for when the light should be updated * Add planet lighting * she * close-ish * c * bittersweat * Fixes * Revert "Merge branch '22719' into 2024-09-29-planet-lighting" This reverts commit 9f2785bb16aee47d794aa3eed8ae15004f97fc35, reversing changes made to 19649c07a5fb625423e08fc18d91c9cb101daa86. * Europa and day-night * weh * rooves working * Clean * Remove Europa * Fixes * fix * Update * Fix caves * Update for engine * Add sun shadows (planet lighting v2) For now mostly targeting walls and having the shadows change over time. Got the basic proof-of-concept working just needs a hell of a lot of polish. * Documentation * a * Fixes * Move blur to an overlay * Slughands * Fixes * Remove v2 work * Finalise --------- Co-authored-by: DoutorWhite <thedoctorwhite@gmail.com>
This commit is contained in:
58
Content.Client/Light/AfterLightTargetOverlay.cs
Normal file
58
Content.Client/Light/AfterLightTargetOverlay.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Content.Client.Light;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This exists just to copy <see cref="BeforeLightTargetOverlay"/> to the light render target
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AfterLightTargetOverlay : Overlay
|
||||||
|
{
|
||||||
|
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||||
|
|
||||||
|
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||||
|
|
||||||
|
public const int ContentZIndex = LightBlurOverlay.ContentZIndex + 1;
|
||||||
|
|
||||||
|
public AfterLightTargetOverlay()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
ZIndex = ContentZIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var viewport = args.Viewport;
|
||||||
|
var worldHandle = args.WorldHandle;
|
||||||
|
|
||||||
|
if (viewport.Eye == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var lightOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||||
|
var bounds = args.WorldBounds;
|
||||||
|
|
||||||
|
// at 1-1 render scale it's mostly fine but at 4x4 it's way too fkn big
|
||||||
|
var newScale = viewport.RenderScale / 2f;
|
||||||
|
|
||||||
|
var localMatrix =
|
||||||
|
viewport.LightRenderTarget.GetWorldToLocalMatrix(viewport.Eye, newScale);
|
||||||
|
var diff = (lightOverlay.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size);
|
||||||
|
var halfDiff = diff / 2;
|
||||||
|
|
||||||
|
// Pixels -> Metres -> Half distance.
|
||||||
|
// If we're zoomed in need to enlarge the bounds further.
|
||||||
|
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// We essentially need to draw the cropped version onto the lightrendertarget.
|
||||||
|
var subRegion = new UIBox2i(halfDiff.X,
|
||||||
|
halfDiff.Y,
|
||||||
|
viewport.LightRenderTarget.Size.X + halfDiff.X,
|
||||||
|
viewport.LightRenderTarget.Size.Y + halfDiff.Y);
|
||||||
|
|
||||||
|
worldHandle.SetTransform(localMatrix);
|
||||||
|
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal file
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Content.Client.Light;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles an enlarged lighting target so content can use large blur radii.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BeforeLightTargetOverlay : Overlay
|
||||||
|
{
|
||||||
|
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||||
|
|
||||||
|
[Dependency] private readonly IClyde _clyde = default!;
|
||||||
|
|
||||||
|
public IRenderTexture EnlargedLightTarget = default!;
|
||||||
|
public Box2Rotated EnlargedBounds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// In metres
|
||||||
|
/// </summary>
|
||||||
|
private float _skirting = 1.5f;
|
||||||
|
|
||||||
|
public const int ContentZIndex = -10;
|
||||||
|
|
||||||
|
public BeforeLightTargetOverlay()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
ZIndex = ContentZIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
// Code is weird but I don't think engine should be enlarging the lighting render target arbitrarily either, maybe via cvar?
|
||||||
|
// The problem is the blur has no knowledge of pixels outside the viewport so with a large enough blur radius you get sampling issues.
|
||||||
|
var size = args.Viewport.LightRenderTarget.Size + (int) (_skirting * EyeManager.PixelsPerMeter);
|
||||||
|
EnlargedBounds = args.WorldBounds.Enlarged(_skirting / 2f);
|
||||||
|
|
||||||
|
// This just exists to copy the lightrendertarget and write back to it.
|
||||||
|
if (EnlargedLightTarget?.Size != size)
|
||||||
|
{
|
||||||
|
EnlargedLightTarget = _clyde
|
||||||
|
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-copy");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.WorldHandle.RenderInRenderTarget(EnlargedLightTarget,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
}, _clyde.GetClearColor(args.MapUid));
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal file
36
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Robust.Client.Graphics;
|
||||||
|
|
||||||
|
namespace Content.Client.Light.EntitySystems;
|
||||||
|
|
||||||
|
public sealed class PlanetLightSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<GetClearColorEvent>(OnClearColor);
|
||||||
|
|
||||||
|
_overlayMan.AddOverlay(new BeforeLightTargetOverlay());
|
||||||
|
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
|
||||||
|
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
|
||||||
|
_overlayMan.AddOverlay(new LightBlurOverlay());
|
||||||
|
_overlayMan.AddOverlay(new AfterLightTargetOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClearColor(ref GetClearColorEvent ev)
|
||||||
|
{
|
||||||
|
ev.Color = Color.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_overlayMan.RemoveOverlay<BeforeLightTargetOverlay>();
|
||||||
|
_overlayMan.RemoveOverlay<RoofOverlay>();
|
||||||
|
_overlayMan.RemoveOverlay<TileEmissionOverlay>();
|
||||||
|
_overlayMan.RemoveOverlay<LightBlurOverlay>();
|
||||||
|
_overlayMan.RemoveOverlay<AfterLightTargetOverlay>();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Content.Shared.Light.EntitySystems;
|
||||||
|
|
||||||
|
namespace Content.Client.Light.EntitySystems;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class RoofSystem : SharedRoofSystem
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
44
Content.Client/Light/LightBlurOverlay.cs
Normal file
44
Content.Client/Light/LightBlurOverlay.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Content.Client.Light;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Essentially handles blurring for content-side light overlays.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class LightBlurOverlay : Overlay
|
||||||
|
{
|
||||||
|
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||||
|
|
||||||
|
[Dependency] private readonly IClyde _clyde = default!;
|
||||||
|
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||||
|
|
||||||
|
public const int ContentZIndex = TileEmissionOverlay.ContentZIndex + 1;
|
||||||
|
|
||||||
|
private IRenderTarget? _blurTarget;
|
||||||
|
|
||||||
|
public LightBlurOverlay()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
ZIndex = ContentZIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
if (args.Viewport.Eye == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var beforeOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||||
|
var size = beforeOverlay.EnlargedLightTarget.Size;
|
||||||
|
|
||||||
|
if (_blurTarget?.Size != size)
|
||||||
|
{
|
||||||
|
_blurTarget = _clyde
|
||||||
|
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-blur");
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = beforeOverlay.EnlargedLightTarget;
|
||||||
|
// Yeah that's all this does keep walkin.
|
||||||
|
_clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Content.Client/Light/LightCycleSystem.cs
Normal file
33
Content.Client/Light/LightCycleSystem.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Content.Client.GameTicking.Managers;
|
||||||
|
using Content.Shared;
|
||||||
|
using Content.Shared.Light.Components;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Client.Light;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class LightCycleSystem : SharedLightCycleSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly ClientGameTicker _ticker = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
var mapQuery = AllEntityQuery<LightCycleComponent, MapLightComponent>();
|
||||||
|
while (mapQuery.MoveNext(out var uid, out var cycle, out var map))
|
||||||
|
{
|
||||||
|
if (!cycle.Running)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var time = (float) _timing.CurTime
|
||||||
|
.Add(cycle.Offset)
|
||||||
|
.Subtract(_ticker.RoundStartTimeSpan)
|
||||||
|
.TotalSeconds;
|
||||||
|
|
||||||
|
var color = GetColor((uid, cycle), cycle.OriginalColor, time);
|
||||||
|
map.AmbientLightColor = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Content.Client/Light/RoofOverlay.cs
Normal file
100
Content.Client/Light/RoofOverlay.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Shared.Light.Components;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Client.Light;
|
||||||
|
|
||||||
|
public sealed class RoofOverlay : Overlay
|
||||||
|
{
|
||||||
|
private readonly IEntityManager _entManager;
|
||||||
|
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||||
|
|
||||||
|
private readonly EntityLookupSystem _lookup;
|
||||||
|
private readonly SharedMapSystem _mapSystem;
|
||||||
|
private readonly SharedTransformSystem _xformSystem;
|
||||||
|
|
||||||
|
private readonly HashSet<Entity<OccluderComponent>> _occluders = new();
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||||
|
|
||||||
|
public const int ContentZIndex = BeforeLightTargetOverlay.ContentZIndex + 1;
|
||||||
|
|
||||||
|
public RoofOverlay(IEntityManager entManager)
|
||||||
|
{
|
||||||
|
_entManager = entManager;
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
_lookup = _entManager.System<EntityLookupSystem>();
|
||||||
|
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||||
|
_xformSystem = _entManager.System<SharedTransformSystem>();
|
||||||
|
|
||||||
|
ZIndex = ContentZIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
if (args.Viewport.Eye == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mapEnt = _mapSystem.GetMap(args.MapId);
|
||||||
|
|
||||||
|
if (!_entManager.TryGetComponent(mapEnt, out RoofComponent? roofComp) ||
|
||||||
|
!_entManager.TryGetComponent(mapEnt, out MapGridComponent? grid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewport = args.Viewport;
|
||||||
|
var eye = args.Viewport.Eye;
|
||||||
|
|
||||||
|
var worldHandle = args.WorldHandle;
|
||||||
|
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||||
|
var bounds = lightoverlay.EnlargedBounds;
|
||||||
|
var target = lightoverlay.EnlargedLightTarget;
|
||||||
|
|
||||||
|
worldHandle.RenderInRenderTarget(target,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
var invMatrix = target.GetWorldToLocalMatrix(eye, viewport.RenderScale / 2f);
|
||||||
|
|
||||||
|
var gridMatrix = _xformSystem.GetWorldMatrix(mapEnt);
|
||||||
|
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||||
|
|
||||||
|
worldHandle.SetTransform(matty);
|
||||||
|
|
||||||
|
var tileEnumerator = _mapSystem.GetTilesEnumerator(mapEnt, grid, bounds);
|
||||||
|
|
||||||
|
// Due to stencilling we essentially draw on unrooved tiles
|
||||||
|
while (tileEnumerator.MoveNext(out var tileRef))
|
||||||
|
{
|
||||||
|
if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == 0x0)
|
||||||
|
{
|
||||||
|
// Check if the tile is occluded in which case hide it anyway.
|
||||||
|
// This is to avoid lit walls bleeding over to unlit tiles.
|
||||||
|
_occluders.Clear();
|
||||||
|
_lookup.GetLocalEntitiesIntersecting(mapEnt, tileRef.GridIndices, _occluders);
|
||||||
|
var found = false;
|
||||||
|
|
||||||
|
foreach (var occluder in _occluders)
|
||||||
|
{
|
||||||
|
if (!occluder.Comp.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var local = _lookup.GetLocalBounds(tileRef, grid.TileSize);
|
||||||
|
worldHandle.DrawRect(local, roofComp.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
90
Content.Client/Light/TileEmissionOverlay.cs
Normal file
90
Content.Client/Light/TileEmissionOverlay.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Shared.Light.Components;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Client.Light;
|
||||||
|
|
||||||
|
public sealed class TileEmissionOverlay : Overlay
|
||||||
|
{
|
||||||
|
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||||
|
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||||
|
|
||||||
|
private SharedMapSystem _mapSystem;
|
||||||
|
private SharedTransformSystem _xformSystem;
|
||||||
|
|
||||||
|
private readonly EntityLookupSystem _lookup;
|
||||||
|
|
||||||
|
private readonly EntityQuery<TransformComponent> _xformQuery;
|
||||||
|
private readonly HashSet<Entity<TileEmissionComponent>> _entities = new();
|
||||||
|
|
||||||
|
private List<Entity<MapGridComponent>> _grids = new();
|
||||||
|
|
||||||
|
public const int ContentZIndex = RoofOverlay.ContentZIndex + 1;
|
||||||
|
|
||||||
|
public TileEmissionOverlay(IEntityManager entManager)
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
_lookup = entManager.System<EntityLookupSystem>();
|
||||||
|
_mapSystem = entManager.System<SharedMapSystem>();
|
||||||
|
_xformSystem = entManager.System<SharedTransformSystem>();
|
||||||
|
|
||||||
|
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
|
||||||
|
ZIndex = ContentZIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
if (args.Viewport.Eye == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mapId = args.MapId;
|
||||||
|
var worldHandle = args.WorldHandle;
|
||||||
|
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||||
|
var bounds = lightoverlay.EnlargedBounds;
|
||||||
|
var target = lightoverlay.EnlargedLightTarget;
|
||||||
|
var viewport = args.Viewport;
|
||||||
|
|
||||||
|
args.WorldHandle.RenderInRenderTarget(target,
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
var invMatrix = target.GetWorldToLocalMatrix(viewport.Eye, viewport.RenderScale / 2f);
|
||||||
|
_grids.Clear();
|
||||||
|
_mapManager.FindGridsIntersecting(mapId, bounds, ref _grids, approx: true);
|
||||||
|
|
||||||
|
foreach (var grid in _grids)
|
||||||
|
{
|
||||||
|
var gridInvMatrix = _xformSystem.GetInvWorldMatrix(grid);
|
||||||
|
var localBounds = gridInvMatrix.TransformBox(bounds);
|
||||||
|
_entities.Clear();
|
||||||
|
_lookup.GetLocalEntitiesIntersecting(grid.Owner, localBounds, _entities);
|
||||||
|
|
||||||
|
if (_entities.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||||
|
|
||||||
|
foreach (var ent in _entities)
|
||||||
|
{
|
||||||
|
var xform = _xformQuery.Comp(ent);
|
||||||
|
|
||||||
|
var tile = _mapSystem.LocalToTile(grid.Owner, grid, xform.Coordinates);
|
||||||
|
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||||
|
|
||||||
|
worldHandle.SetTransform(matty);
|
||||||
|
|
||||||
|
// Yes I am fully aware this leads to overlap. If you really want to have alpha then you'll need
|
||||||
|
// to turn the squares into polys.
|
||||||
|
// Additionally no shadows so if you make it too big it's going to go through a 1x wall.
|
||||||
|
var local = _lookup.GetLocalBounds(tile, grid.Comp.TileSize).Enlarged(ent.Comp.Range);
|
||||||
|
worldHandle.DrawRect(local, ent.Comp.Color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,12 +39,12 @@ namespace Content.IntegrationTests.Tests
|
|||||||
{
|
{
|
||||||
var mapGrid = mapManager.CreateGridEntity(mapId);
|
var mapGrid = mapManager.CreateGridEntity(mapId);
|
||||||
xformSystem.SetWorldPosition(mapGrid, new Vector2(10, 10));
|
xformSystem.SetWorldPosition(mapGrid, new Vector2(10, 10));
|
||||||
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(1, (TileRenderFlag) 1, 255));
|
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(typeId: 1, flags: 1, variant: 255));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
var mapGrid = mapManager.CreateGridEntity(mapId);
|
var mapGrid = mapManager.CreateGridEntity(mapId);
|
||||||
xformSystem.SetWorldPosition(mapGrid, new Vector2(-8, -8));
|
xformSystem.SetWorldPosition(mapGrid, new Vector2(-8, -8));
|
||||||
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(2, (TileRenderFlag) 1, 254));
|
mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(typeId: 2, flags: 1, variant: 254));
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath));
|
Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath));
|
||||||
@@ -73,7 +73,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(10, 10)));
|
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(10, 10)));
|
||||||
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(1, (TileRenderFlag) 1, 255)));
|
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(typeId: 1, flags: 1, variant: 255)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -87,7 +87,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(-8, -8)));
|
Assert.That(xformSystem.GetWorldPosition(gridXform), Is.EqualTo(new Vector2(-8, -8)));
|
||||||
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(2, (TileRenderFlag) 1, 254)));
|
Assert.That(mapSystem.GetTileRef(gridUid, mapGrid, new Vector2i(0, 0)).Tile, Is.EqualTo(new Tile(typeId: 2, flags: 1, variant: 254)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
11
Content.Server/Light/Components/SetRoofComponent.cs
Normal file
11
Content.Server/Light/Components/SetRoofComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Content.Server.Light.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the roof flag to this tile and deletes the entity.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class SetRoofComponent : Component
|
||||||
|
{
|
||||||
|
[DataField(required: true)]
|
||||||
|
public bool Value;
|
||||||
|
}
|
||||||
22
Content.Server/Light/EntitySystems/LightCycleSystem.cs
Normal file
22
Content.Server/Light/EntitySystems/LightCycleSystem.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Content.Shared;
|
||||||
|
using Content.Shared.Light.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Light.EntitySystems;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class LightCycleSystem : SharedLightCycleSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
protected override void OnCycleMapInit(Entity<LightCycleComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
base.OnCycleMapInit(ent, ref args);
|
||||||
|
|
||||||
|
if (ent.Comp.InitialOffset)
|
||||||
|
{
|
||||||
|
ent.Comp.Offset = _random.Next(ent.Comp.Duration);
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Content.Server/Light/EntitySystems/RoofSystem.cs
Normal file
33
Content.Server/Light/EntitySystems/RoofSystem.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Content.Server.Light.Components;
|
||||||
|
using Content.Shared.Light.EntitySystems;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Light.EntitySystems;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class RoofSystem : SharedRoofSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||||
|
|
||||||
|
private EntityQuery<MapGridComponent> _gridQuery;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||||
|
SubscribeLocalEvent<SetRoofComponent, ComponentStartup>(OnFlagStartup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFlagStartup(Entity<SetRoofComponent> ent, ref ComponentStartup args)
|
||||||
|
{
|
||||||
|
var xform = Transform(ent.Owner);
|
||||||
|
|
||||||
|
if (_gridQuery.TryComp(xform.GridUid, out var grid))
|
||||||
|
{
|
||||||
|
var index = _maps.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates);
|
||||||
|
SetRoof((xform.GridUid.Value, grid, null), index, ent.Comp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueDel(ent.Owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ using Content.Shared.Atmos;
|
|||||||
using Content.Shared.Decals;
|
using Content.Shared.Decals;
|
||||||
using Content.Shared.Ghost;
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Gravity;
|
using Content.Shared.Gravity;
|
||||||
|
using Content.Shared.Light.Components;
|
||||||
using Content.Shared.Parallax.Biomes;
|
using Content.Shared.Parallax.Biomes;
|
||||||
using Content.Shared.Parallax.Biomes.Layers;
|
using Content.Shared.Parallax.Biomes.Layers;
|
||||||
using Content.Shared.Parallax.Biomes.Markers;
|
using Content.Shared.Parallax.Biomes.Markers;
|
||||||
@@ -330,6 +331,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
|||||||
|
|
||||||
while (biomes.MoveNext(out var biome))
|
while (biomes.MoveNext(out var biome))
|
||||||
{
|
{
|
||||||
|
if (biome.LifeStage < ComponentLifeStage.Running)
|
||||||
|
continue;
|
||||||
|
|
||||||
_activeChunks.Add(biome, _tilePool.Get());
|
_activeChunks.Add(biome, _tilePool.Get());
|
||||||
_markerChunks.GetOrNew(biome);
|
_markerChunks.GetOrNew(biome);
|
||||||
}
|
}
|
||||||
@@ -379,6 +383,10 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
|||||||
|
|
||||||
while (loadBiomes.MoveNext(out var gridUid, out var biome, out var grid))
|
while (loadBiomes.MoveNext(out var gridUid, out var biome, out var grid))
|
||||||
{
|
{
|
||||||
|
// If not MapInit don't run it.
|
||||||
|
if (biome.LifeStage < ComponentLifeStage.Running)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!biome.Enabled)
|
if (!biome.Enabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -745,7 +753,10 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (modified.Count == 0)
|
if (modified.Count == 0)
|
||||||
|
{
|
||||||
|
component.ModifiedTiles.Remove(chunk);
|
||||||
_tilePool.Return(modified);
|
_tilePool.Return(modified);
|
||||||
|
}
|
||||||
|
|
||||||
component.PendingMarkers.Remove(chunk);
|
component.PendingMarkers.Remove(chunk);
|
||||||
}
|
}
|
||||||
@@ -1014,11 +1025,14 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
|||||||
// Midday: #E6CB8B
|
// Midday: #E6CB8B
|
||||||
// Moonlight: #2b3143
|
// Moonlight: #2b3143
|
||||||
// Lava: #A34931
|
// Lava: #A34931
|
||||||
|
|
||||||
var light = EnsureComp<MapLightComponent>(mapUid);
|
var light = EnsureComp<MapLightComponent>(mapUid);
|
||||||
light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059");
|
light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059");
|
||||||
Dirty(mapUid, light, metadata);
|
Dirty(mapUid, light, metadata);
|
||||||
|
|
||||||
|
EnsureComp<RoofComponent>(mapUid);
|
||||||
|
|
||||||
|
EnsureComp<LightCycleComponent>(mapUid);
|
||||||
|
|
||||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||||
|
|||||||
56
Content.Shared/Light/Components/LightCycleComponent.cs
Normal file
56
Content.Shared/Light/Components/LightCycleComponent.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Shared.Light.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cycles through colors AKA "Day / Night cycle" on <see cref="MapLightComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
|
public sealed partial class LightCycleComponent : Component
|
||||||
|
{
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public Color OriginalColor = Color.Transparent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long an entire cycle lasts
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public TimeSpan Duration = TimeSpan.FromMinutes(30);
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public TimeSpan Offset;
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool Enabled = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should the offset be randomised upon MapInit.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public bool InitialOffset = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Trench of the oscillation.
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public float MinLightLevel = 0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Peak of the oscillation
|
||||||
|
/// </summary>
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public float MaxLightLevel = 3f;
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public float ClipLight = 1.25f;
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public Color ClipLevel = new Color(1f, 1f, 1.25f);
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public Color MinLevel = new Color(0.1f, 0.15f, 0.50f);
|
||||||
|
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public Color MaxLevel = new Color(2f, 2f, 5f);
|
||||||
|
}
|
||||||
13
Content.Shared/Light/Components/RoofComponent.cs
Normal file
13
Content.Shared/Light/Components/RoofComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Light.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will draw shadows over tiles flagged as roof tiles on the attached map.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
|
public sealed partial class RoofComponent : Component
|
||||||
|
{
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public Color Color = Color.Black;
|
||||||
|
}
|
||||||
16
Content.Shared/Light/Components/TileEmissionComponent.cs
Normal file
16
Content.Shared/Light/Components/TileEmissionComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Light.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will draw lighting in a range around the tile.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||||
|
public sealed partial class TileEmissionComponent : Component
|
||||||
|
{
|
||||||
|
[DataField, AutoNetworkedField]
|
||||||
|
public float Range = 0.25f;
|
||||||
|
|
||||||
|
[DataField(required: true), AutoNetworkedField]
|
||||||
|
public Color Color = Color.Transparent;
|
||||||
|
}
|
||||||
42
Content.Shared/Light/EntitySystems/SharedRoofSystem.cs
Normal file
42
Content.Shared/Light/EntitySystems/SharedRoofSystem.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Content.Shared.Light.Components;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Shared.Light.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the roof flag for tiles that gets used for the RoofOverlay.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SharedRoofSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||||
|
|
||||||
|
public void SetRoof(Entity<MapGridComponent?, RoofComponent?> grid, Vector2i index, bool value)
|
||||||
|
{
|
||||||
|
if (!Resolve(grid, ref grid.Comp1, ref grid.Comp2, false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_maps.TryGetTile(grid.Comp1, index, out var tile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mask = (tile.Flags & (byte)TileFlag.Roof);
|
||||||
|
var rooved = mask != 0x0;
|
||||||
|
|
||||||
|
if (rooved == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Tile newTile;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
newTile = tile.WithFlag((byte)(tile.Flags | (ushort)TileFlag.Roof));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newTile = tile.WithFlag((byte)(tile.Flags & ~(ushort)TileFlag.Roof));
|
||||||
|
}
|
||||||
|
|
||||||
|
_maps.SetTile((grid.Owner, grid.Comp1), index, newTile);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.Light.Components;
|
||||||
using Content.Shared.Movement.Systems;
|
using Content.Shared.Movement.Systems;
|
||||||
using Content.Shared.Tools;
|
using Content.Shared.Tools;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
@@ -118,4 +120,11 @@ namespace Content.Shared.Maps
|
|||||||
TileId = id;
|
TileId = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum TileFlag : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Roof = 1 << 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,36 @@
|
|||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using Robust.Shared.Noise;
|
using Robust.Shared.Noise;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed partial class BiomeTileLayer : IBiomeLayer
|
public sealed partial class BiomeTileLayer : IBiomeLayer
|
||||||
{
|
{
|
||||||
[DataField("noise")] public FastNoiseLite Noise { get; private set; } = new(0);
|
[DataField] public FastNoiseLite Noise { get; private set; } = new(0);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[DataField("threshold")]
|
[DataField]
|
||||||
public float Threshold { get; private set; } = 0.5f;
|
public float Threshold { get; private set; } = 0.5f;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[DataField("invert")] public bool Invert { get; private set; } = false;
|
[DataField] public bool Invert { get; private set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Which tile variants to use for this layer. Uses all of the tile's variants if none specified
|
/// Which tile variants to use for this layer. Uses all of the tile's variants if none specified
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("variants")]
|
[DataField]
|
||||||
public List<byte>? Variants = null;
|
public List<byte>? Variants = null;
|
||||||
|
|
||||||
[DataField("tile", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>))]
|
[DataField(required: true)]
|
||||||
public string Tile = string.Empty;
|
public ProtoId<ContentTileDefinition> Tile = string.Empty;
|
||||||
|
|
||||||
|
// TODO: Need some good engine solution to this, see FlagSerializer for what needs changing.
|
||||||
|
/// <summary>
|
||||||
|
/// Flags to set on the tile when placed.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public byte Flags = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ public abstract class SharedBiomeSystem : EntitySystem
|
|||||||
if (layer is not BiomeTileLayer tileLayer)
|
if (layer is not BiomeTileLayer tileLayer)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index<ContentTileDefinition>(tileLayer.Tile), tileLayer.Variants, out tile))
|
if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index(tileLayer.Tile), tileLayer.Flags, tileLayer.Variants, out tile))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ public abstract class SharedBiomeSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool TryGetTile(Vector2i indices, FastNoiseLite noise, 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, byte tileFlags, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
|
||||||
{
|
{
|
||||||
var found = noise.GetNoise(indices.X, indices.Y);
|
var found = noise.GetNoise(indices.X, indices.Y);
|
||||||
found = invert ? found * -1 : found;
|
found = invert ? found * -1 : found;
|
||||||
@@ -163,7 +163,7 @@ public abstract class SharedBiomeSystem : EntitySystem
|
|||||||
variant = _tile.PickVariant(tileDef, (int) variantValue);
|
variant = _tile.PickVariant(tileDef, (int) variantValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
tile = new Tile(tileDef.TileId, 0, variant);
|
tile = new Tile(tileDef.TileId, flags: tileFlags, variant);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
116
Content.Shared/SharedLightCycleSystem.cs
Normal file
116
Content.Shared/SharedLightCycleSystem.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using Content.Shared.Light.Components;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Shared;
|
||||||
|
|
||||||
|
public abstract class SharedLightCycleSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<LightCycleComponent, MapInitEvent>(OnCycleMapInit);
|
||||||
|
SubscribeLocalEvent<LightCycleComponent, ComponentShutdown>(OnCycleShutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnCycleMapInit(Entity<LightCycleComponent> ent, ref MapInitEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp(ent.Owner, out MapLightComponent? mapLight))
|
||||||
|
{
|
||||||
|
ent.Comp.OriginalColor = mapLight.AmbientLightColor;
|
||||||
|
Dirty(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCycleShutdown(Entity<LightCycleComponent> ent, ref ComponentShutdown args)
|
||||||
|
{
|
||||||
|
if (TryComp(ent.Owner, out MapLightComponent? mapLight))
|
||||||
|
{
|
||||||
|
mapLight.AmbientLightColor = ent.Comp.OriginalColor;
|
||||||
|
Dirty(ent.Owner, mapLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color GetColor(Entity<LightCycleComponent> cycle, Color color, float time)
|
||||||
|
{
|
||||||
|
if (cycle.Comp.Enabled)
|
||||||
|
{
|
||||||
|
var lightLevel = CalculateLightLevel(cycle.Comp, time);
|
||||||
|
var colorLevel = CalculateColorLevel(cycle.Comp, time);
|
||||||
|
return new Color(
|
||||||
|
(byte)Math.Min(255, color.RByte * colorLevel.R * lightLevel),
|
||||||
|
(byte)Math.Min(255, color.GByte * colorLevel.G * lightLevel),
|
||||||
|
(byte)Math.Min(255, color.BByte * colorLevel.B * lightLevel)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates light intensity as a function of time.
|
||||||
|
/// </summary>
|
||||||
|
public static double CalculateLightLevel(LightCycleComponent comp, float time)
|
||||||
|
{
|
||||||
|
var waveLength = MathF.Max(1, (float) comp.Duration.TotalSeconds);
|
||||||
|
var crest = MathF.Max(0f, comp.MaxLightLevel);
|
||||||
|
var shift = MathF.Max(0f, comp.MinLightLevel);
|
||||||
|
return Math.Min(comp.ClipLight, CalculateCurve(time, waveLength, crest, shift, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// It is important to note that each color must have a different exponent, to modify how early or late one color should stand out in relation to another.
|
||||||
|
/// This "simulates" what the atmosphere does and is what generates the effect of dawn and dusk.
|
||||||
|
/// The blue component must be a cosine function with half period, so that its minimum is at dawn and dusk, generating the "warm" color corresponding to these periods.
|
||||||
|
/// As you can see in the values, the maximums of the function serve more to define the curve behavior,
|
||||||
|
/// they must be "clipped" so as not to distort the original color of the lighting. In practice, the maximum values, in fact, are the clip thresholds.
|
||||||
|
/// </summary>
|
||||||
|
public static Color CalculateColorLevel(LightCycleComponent comp, float time)
|
||||||
|
{
|
||||||
|
var waveLength = MathF.Max(1f, (float) comp.Duration.TotalSeconds);
|
||||||
|
|
||||||
|
var red = MathF.Min(comp.ClipLevel.R,
|
||||||
|
CalculateCurve(time,
|
||||||
|
waveLength,
|
||||||
|
MathF.Max(0f, comp.MaxLevel.R),
|
||||||
|
MathF.Max(0f, comp.MinLevel.R),
|
||||||
|
4f));
|
||||||
|
|
||||||
|
var green = MathF.Min(comp.ClipLevel.G,
|
||||||
|
CalculateCurve(time,
|
||||||
|
waveLength,
|
||||||
|
MathF.Max(0f, comp.MaxLevel.G),
|
||||||
|
MathF.Max(0f, comp.MinLevel.G),
|
||||||
|
10f));
|
||||||
|
|
||||||
|
var blue = MathF.Min(comp.ClipLevel.B,
|
||||||
|
CalculateCurve(time,
|
||||||
|
waveLength / 2f,
|
||||||
|
MathF.Max(0f, comp.MaxLevel.B),
|
||||||
|
MathF.Max(0f, comp.MinLevel.B),
|
||||||
|
2,
|
||||||
|
waveLength / 4f));
|
||||||
|
|
||||||
|
return new Color(red, green, blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a sinusoidal curve as a function of x (time). The other parameters serve to adjust the behavior of the curve.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x"> It corresponds to the independent variable of the function, which in the context of this algorithm is the current time. </param>
|
||||||
|
/// <param name="waveLength"> It's the wavelength of the function, it can be said to be the total duration of the light cycle. </param>
|
||||||
|
/// <param name="crest"> It's the maximum point of the function, where it will have its greatest value. </param>
|
||||||
|
/// <param name="shift"> It's the vertical displacement of the function, in practice it corresponds to the minimum value of the function. </param>
|
||||||
|
/// <param name="exponent"> It is the exponent of the sine, serves to "flatten" the function close to its minimum points and make it "steeper" close to its maximum. </param>
|
||||||
|
/// <param name="phase"> It changes the phase of the wave, like a "horizontal shift". It is important to transform the sinusoidal function into cosine, when necessary. </param>
|
||||||
|
/// <returns> The result of the function. </returns>
|
||||||
|
public static float CalculateCurve(float x,
|
||||||
|
float waveLength,
|
||||||
|
float crest,
|
||||||
|
float shift,
|
||||||
|
float exponent,
|
||||||
|
float phase = 0)
|
||||||
|
{
|
||||||
|
var sen = MathF.Pow(MathF.Sin((MathF.PI * (phase + x)) / waveLength), exponent);
|
||||||
|
return (crest - shift) * sen + shift;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Resources/Prototypes/Entities/Markers/tile.yml
Normal file
37
Resources/Prototypes/Entities/Markers/tile.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
- type: entity
|
||||||
|
id: BaseRoofMarker
|
||||||
|
abstract: true
|
||||||
|
placement:
|
||||||
|
mode: SnapgridCenter
|
||||||
|
components:
|
||||||
|
- type: Transform
|
||||||
|
anchored: true
|
||||||
|
- type: Sprite
|
||||||
|
drawdepth: Overdoors
|
||||||
|
sprite: Markers/cross.rsi
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: RoofMarker
|
||||||
|
name: Roof
|
||||||
|
suffix: Enabled
|
||||||
|
parent: BaseRoofMarker
|
||||||
|
components:
|
||||||
|
- type: SetRoof
|
||||||
|
value: true
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: green
|
||||||
|
shader: unshaded
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: NoRoofMarker
|
||||||
|
name: Roof
|
||||||
|
suffix: Disabled
|
||||||
|
parent: BaseRoofMarker
|
||||||
|
components:
|
||||||
|
- type: SetRoof
|
||||||
|
value: false
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: red
|
||||||
|
shader: unshaded
|
||||||
@@ -543,7 +543,7 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
- Wall
|
- Wall
|
||||||
- Diagonal
|
- Diagonal
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
drawdepth: Walls
|
drawdepth: Walls
|
||||||
sprite: Structures/Walls/plastitanium_diagonal.rsi
|
sprite: Structures/Walls/plastitanium_diagonal.rsi
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
snap:
|
snap:
|
||||||
- Wall
|
- Wall
|
||||||
components:
|
components:
|
||||||
|
- type: TileEmission
|
||||||
|
color: "#FF4500"
|
||||||
- type: StepTrigger
|
- type: StepTrigger
|
||||||
requiredTriggeredSpeed: 0
|
requiredTriggeredSpeed: 0
|
||||||
intersectRatio: 0.1
|
intersectRatio: 0.1
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
snap:
|
snap:
|
||||||
- Wall
|
- Wall
|
||||||
components:
|
components:
|
||||||
|
- type: TileEmission
|
||||||
|
color: "#974988"
|
||||||
- type: StepTrigger
|
- type: StepTrigger
|
||||||
requiredTriggeredSpeed: 0
|
requiredTriggeredSpeed: 0
|
||||||
intersectRatio: 0.1
|
intersectRatio: 0.1
|
||||||
|
|||||||
@@ -544,6 +544,7 @@
|
|||||||
- !type:BiomeTileLayer
|
- !type:BiomeTileLayer
|
||||||
threshold: -1.0
|
threshold: -1.0
|
||||||
tile: FloorAsteroidSand
|
tile: FloorAsteroidSand
|
||||||
|
flags: 1
|
||||||
|
|
||||||
# Asteroid
|
# Asteroid
|
||||||
- type: biomeTemplate
|
- type: biomeTemplate
|
||||||
|
|||||||
Reference in New Issue
Block a user