Merge staging into master (#40356)
This commit is contained in:
@@ -19,7 +19,6 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
|
||||
private GasTileOverlay _overlay = default!;
|
||||
private GasTileHeatOverlay _heatOverlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -29,16 +28,12 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
|
||||
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys, _xformSys);
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
|
||||
_heatOverlay = new GasTileHeatOverlay();
|
||||
_overlayMan.AddOverlay(_heatOverlay);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<GasTileOverlay>();
|
||||
_overlayMan.RemoveOverlay<GasTileHeatOverlay>();
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Atmos.Overlays;
|
||||
|
||||
public sealed class GasTileHeatOverlay : Overlay
|
||||
{
|
||||
public override bool RequestScreenTexture { get; set; } = true;
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
private static readonly ProtoId<ShaderPrototype> HeatOverlayShader = "Heat";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
// We can't resolve this immediately, because it's an entitysystem, but we will attempt to resolve and cache this
|
||||
// once we begin to draw.
|
||||
private GasTileOverlaySystem? _gasTileOverlay;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
|
||||
private IRenderTexture? _heatTarget;
|
||||
private IRenderTexture? _heatBlurTarget;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public GasTileHeatOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
_shader = _proto.Index(HeatOverlayShader).InstanceUnique();
|
||||
|
||||
_configManager.OnValueChanged(CCVars.ReducedMotion, SetReducedMotion, invokeImmediately: true);
|
||||
|
||||
}
|
||||
|
||||
private void SetReducedMotion(bool reducedMotion)
|
||||
{
|
||||
_shader.SetParameter("strength_scale", reducedMotion ? 0.5f : 1f);
|
||||
_shader.SetParameter("speed_scale", reducedMotion ? 0.25f : 1f);
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
// If we haven't resolved this yet, give it a try or bail
|
||||
_gasTileOverlay ??= _entManager.System<GasTileOverlaySystem>();
|
||||
|
||||
if (_gasTileOverlay == null)
|
||||
return false;
|
||||
|
||||
var target = args.Viewport.RenderTarget;
|
||||
|
||||
// Probably the resolution of the game window changed, remake the textures.
|
||||
if (_heatTarget?.Texture.Size != target.Size)
|
||||
{
|
||||
_heatTarget?.Dispose();
|
||||
_heatTarget = _clyde.CreateRenderTarget(
|
||||
target.Size,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: nameof(GasTileHeatOverlay));
|
||||
}
|
||||
if (_heatBlurTarget?.Texture.Size != target.Size)
|
||||
{
|
||||
_heatBlurTarget?.Dispose();
|
||||
_heatBlurTarget = _clyde.CreateRenderTarget(
|
||||
target.Size,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: $"{nameof(GasTileHeatOverlay)}-blur");
|
||||
}
|
||||
|
||||
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
|
||||
|
||||
args.WorldHandle.UseShader(_proto.Index(UnshadedShader).Instance());
|
||||
|
||||
var mapId = args.MapId;
|
||||
var worldAABB = args.WorldAABB;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var worldHandle = args.WorldHandle;
|
||||
var worldToViewportLocal = args.Viewport.GetWorldToLocalMatrix();
|
||||
|
||||
// If there is no distortion after checking all visible tiles, we can bail early
|
||||
var anyDistortion = false;
|
||||
|
||||
// We're rendering in the context of the heat target texture, which will encode data as to where and how strong
|
||||
// the heat distortion will be
|
||||
args.WorldHandle.RenderInRenderTarget(_heatTarget,
|
||||
() =>
|
||||
{
|
||||
List<Entity<MapGridComponent>> grids = new();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref grids);
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
if (!overlayQuery.TryGetComponent(grid.Owner, out var comp))
|
||||
continue;
|
||||
|
||||
var gridEntToWorld = _xformSys.GetWorldMatrix(grid.Owner);
|
||||
var gridEntToViewportLocal = gridEntToWorld * worldToViewportLocal;
|
||||
|
||||
if (!Matrix3x2.Invert(gridEntToViewportLocal, out var viewportLocalToGridEnt))
|
||||
continue;
|
||||
|
||||
var uvToUi = Matrix3Helpers.CreateScale(_heatTarget.Size.X, -_heatTarget.Size.Y);
|
||||
var uvToGridEnt = uvToUi * viewportLocalToGridEnt;
|
||||
|
||||
// Because we want the actual distortion to be calculated based on the grid coordinates*, we need
|
||||
// to pass a matrix transformation to go from the viewport coordinates to grid coordinates.
|
||||
// * (why? because otherwise the effect would shimmer like crazy as you moved around, think
|
||||
// moving a piece of warped glass above a picture instead of placing the warped glass on the
|
||||
// paper and moving them together)
|
||||
_shader.SetParameter("grid_ent_from_viewport_local", uvToGridEnt);
|
||||
|
||||
// Draw commands (like DrawRect) will be using grid coordinates from here
|
||||
worldHandle.SetTransform(gridEntToViewportLocal);
|
||||
|
||||
// We only care about tiles that fit in these bounds
|
||||
var floatBounds = worldToViewportLocal.TransformBox(worldBounds).Enlarged(grid.Comp.TileSize);
|
||||
var localBounds = new Box2i(
|
||||
(int)MathF.Floor(floatBounds.Left),
|
||||
(int)MathF.Floor(floatBounds.Bottom),
|
||||
(int)MathF.Ceiling(floatBounds.Right),
|
||||
(int)MathF.Ceiling(floatBounds.Top));
|
||||
|
||||
// for each tile and its gas --->
|
||||
foreach (var chunk in comp.Chunks.Values)
|
||||
{
|
||||
var enumerator = new GasChunkEnumerator(chunk);
|
||||
|
||||
while (enumerator.MoveNext(out var tileGas))
|
||||
{
|
||||
// --->
|
||||
// Check and make sure the tile is within the viewport/screen
|
||||
var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
|
||||
if (!localBounds.Contains(tilePosition))
|
||||
continue;
|
||||
|
||||
// Get the distortion strength from the temperature and bail if it's not hot enough
|
||||
var strength = _gasTileOverlay.GetHeatDistortionStrength(tileGas.Temperature);
|
||||
if (strength <= 0f)
|
||||
continue;
|
||||
|
||||
anyDistortion = true;
|
||||
// Encode the strength in the red channel, then 1.0 alpha if it's an active tile.
|
||||
// BlurRenderTarget will then apply a blur around the edge, but we don't want it to bleed
|
||||
// past the tile.
|
||||
// So we use this alpha channel to chop the lower alpha values off in the shader to fit a
|
||||
// fit mask back into the tile.
|
||||
worldHandle.DrawRect(
|
||||
Box2.CenteredAround(tilePosition + new Vector2(0.5f, 0.5f), grid.Comp.TileSizeVector),
|
||||
new Color(strength,0f, 0f, strength > 0f ? 1.0f : 0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// This clears the buffer to all zero first...
|
||||
new Color(0, 0, 0, 0));
|
||||
|
||||
// no distortion, no need to render
|
||||
if (!anyDistortion)
|
||||
{
|
||||
// Return the draw handle to normal settings
|
||||
args.WorldHandle.UseShader(null);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear to draw
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture is null || _heatTarget is null || _heatBlurTarget is null)
|
||||
return;
|
||||
|
||||
// Blur to soften the edges of the distortion. the lower parts of the alpha channel need to get cut off in the
|
||||
// distortion shader to keep them in tile bounds.
|
||||
_clyde.BlurRenderTarget(args.Viewport, _heatTarget, _heatBlurTarget, args.Viewport.Eye!, 14f);
|
||||
|
||||
// Set up and render the distortion
|
||||
_shader.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
args.WorldHandle.UseShader(_shader);
|
||||
args.WorldHandle.DrawTextureRect(_heatTarget.Texture, args.WorldBounds);
|
||||
|
||||
// Return the draw handle to normal settings
|
||||
args.WorldHandle.UseShader(null);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
protected override void DisposeBehavior()
|
||||
{
|
||||
_heatTarget = null;
|
||||
_heatBlurTarget = null;
|
||||
_configManager.UnsubValueChanged(CCVars.ReducedMotion, SetReducedMotion);
|
||||
base.DisposeBehavior();
|
||||
}
|
||||
}
|
||||
@@ -1088,10 +1088,11 @@ namespace Content.Client.Lobby.UI
|
||||
if (Profile is null) return;
|
||||
|
||||
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
||||
var strategy = _prototypeManager.Index(skin).Strategy;
|
||||
|
||||
switch (skin)
|
||||
switch (strategy.InputType)
|
||||
{
|
||||
case HumanoidSkinColor.HumanToned:
|
||||
case SkinColorationStrategyInput.Unary:
|
||||
{
|
||||
if (!Skin.Visible)
|
||||
{
|
||||
@@ -1099,39 +1100,14 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
var color = SkinColor.HumanSkinTone((int) Skin.Value);
|
||||
|
||||
Markings.CurrentSkinColor = color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));//
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.Hues:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
Markings.CurrentSkinColor = _rgbSkinColorSelector.Color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(_rgbSkinColorSelector.Color));
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.TintedHues:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
var color = SkinColor.TintedHues(_rgbSkinColorSelector.Color);
|
||||
var color = strategy.FromUnary(Skin.Value);
|
||||
|
||||
Markings.CurrentSkinColor = color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
||||
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.VoxFeathers:
|
||||
case SkinColorationStrategyInput.Color:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
@@ -1139,10 +1115,11 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
var color = SkinColor.ClosestVoxColor(_rgbSkinColorSelector.Color);
|
||||
var color = strategy.ClosestSkinColor(_rgbSkinColorSelector.Color);
|
||||
|
||||
Markings.CurrentSkinColor = color;
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1321,10 +1298,11 @@ namespace Content.Client.Lobby.UI
|
||||
return;
|
||||
|
||||
var skin = _prototypeManager.Index<SpeciesPrototype>(Profile.Species).SkinColoration;
|
||||
var strategy = _prototypeManager.Index(skin).Strategy;
|
||||
|
||||
switch (skin)
|
||||
switch (strategy.InputType)
|
||||
{
|
||||
case HumanoidSkinColor.HumanToned:
|
||||
case SkinColorationStrategyInput.Unary:
|
||||
{
|
||||
if (!Skin.Visible)
|
||||
{
|
||||
@@ -1332,11 +1310,11 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = false;
|
||||
}
|
||||
|
||||
Skin.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
|
||||
Skin.Value = strategy.ToUnary(Profile.Appearance.SkinColor);
|
||||
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.Hues:
|
||||
case SkinColorationStrategyInput.Color:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
@@ -1344,36 +1322,11 @@ namespace Content.Client.Lobby.UI
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
// set the RGB values to the direct values otherwise
|
||||
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.TintedHues:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
// set the RGB values to the direct values otherwise
|
||||
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
|
||||
break;
|
||||
}
|
||||
case HumanoidSkinColor.VoxFeathers:
|
||||
{
|
||||
if (!RgbSkinColorContainer.Visible)
|
||||
{
|
||||
Skin.Visible = false;
|
||||
RgbSkinColorContainer.Visible = true;
|
||||
}
|
||||
|
||||
_rgbSkinColorSelector.Color = SkinColor.ClosestVoxColor(Profile.Appearance.SkinColor);
|
||||
_rgbSkinColorSelector.Color = strategy.ClosestSkinColor(Profile.Appearance.SkinColor);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void UpdateSpeciesGuidebookIcon()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
@@ -11,6 +13,7 @@ using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -29,6 +32,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _confMan = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IParallelManager _parMan = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly ChunkingSystem _chunkingSys = default!;
|
||||
@@ -60,12 +64,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<GasTileOverlayComponent> _query;
|
||||
|
||||
/// <summary>
|
||||
/// How much the distortion strength should change for the temperature of a tile to be dirtied.
|
||||
/// The strength goes from 0.0f to 1.0f, so 0.05f gives it essentially 20 "steps"
|
||||
/// </summary>
|
||||
private float _heatDistortionStrengthChangeTolerance;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -87,10 +85,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
};
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
Subs.CVar(ConfMan, CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
|
||||
Subs.CVar(ConfMan, CCVars.GasOverlayThresholds, UpdateThresholds, true);
|
||||
Subs.CVar(ConfMan, CVars.NetPVS, OnPvsToggle, true);
|
||||
Subs.CVar(ConfMan, CCVars.GasOverlayHeatThreshold, UpdateHeatThresholds, true);
|
||||
Subs.CVar(_confMan, CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
|
||||
Subs.CVar(_confMan, CCVars.GasOverlayThresholds, UpdateThresholds, true);
|
||||
Subs.CVar(_confMan, CVars.NetPVS, OnPvsToggle, true);
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentStartup>(OnStartup);
|
||||
@@ -140,7 +137,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
private void UpdateTickRate(float value) => _updateInterval = value > 0.0f ? 1 / value : float.MaxValue;
|
||||
private void UpdateThresholds(int value) => _thresholds = value;
|
||||
private void UpdateHeatThresholds(float v) => _heatDistortionStrengthChangeTolerance = MathHelper.Clamp01(v);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Invalidate(Entity<GasTileOverlayComponent?> grid, Vector2i index)
|
||||
@@ -179,9 +175,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
public GasOverlayData GetOverlayData(GasMixture? mixture)
|
||||
{
|
||||
var data = new GasOverlayData(0,
|
||||
new byte[VisibleGasId.Length],
|
||||
mixture?.Temperature ?? Atmospherics.TCMB);
|
||||
var data = new GasOverlayData(0, new byte[VisibleGasId.Length]);
|
||||
|
||||
for (var i = 0; i < VisibleGasId.Length; i++)
|
||||
{
|
||||
@@ -221,17 +215,15 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
|
||||
var changed = false;
|
||||
var temp = tile.Hotspot.Valid ? tile.Hotspot.Temperature : tile.Air?.Temperature ?? Atmospherics.TCMB;
|
||||
if (oldData.Equals(default))
|
||||
{
|
||||
changed = true;
|
||||
oldData = new GasOverlayData(tile.Hotspot.State, new byte[VisibleGasId.Length], temp);
|
||||
oldData = new GasOverlayData(tile.Hotspot.State, new byte[VisibleGasId.Length]);
|
||||
}
|
||||
else if (oldData.FireState != tile.Hotspot.State ||
|
||||
CheckTemperatureTolerance(oldData.Temperature, temp, _heatDistortionStrengthChangeTolerance))
|
||||
else if (oldData.FireState != tile.Hotspot.State)
|
||||
{
|
||||
changed = true;
|
||||
oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity, temp);
|
||||
oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity);
|
||||
}
|
||||
|
||||
if (tile is {Air: not null, NoGridTile: false})
|
||||
@@ -279,20 +271,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function determines whether the change in temperature is significant enough to warrant dirtying the tile data.
|
||||
/// </summary>
|
||||
private bool CheckTemperatureTolerance(float tempA, float tempB, float tolerance)
|
||||
{
|
||||
var (strengthA, strengthB) = (GetHeatDistortionStrength(tempA), GetHeatDistortionStrength(tempB));
|
||||
|
||||
return (strengthA <= 0f && strengthB > 0f) || // change to or from 0
|
||||
(strengthB <= 0f && strengthA > 0f) ||
|
||||
(strengthA >= 1f && strengthB < 1f) || // change to or from 1
|
||||
(strengthB >= 1f && strengthA < 1f) ||
|
||||
Math.Abs(strengthA - strengthB) > tolerance; // other change within tolerance
|
||||
}
|
||||
|
||||
private void UpdateOverlayData()
|
||||
{
|
||||
// TODO parallelize?
|
||||
|
||||
@@ -102,19 +102,25 @@ public sealed class SharpSystem : EntitySystem
|
||||
|
||||
component.Butchering.Remove(args.Args.Target.Value);
|
||||
|
||||
if (_containerSystem.IsEntityInContainer(args.Args.Target.Value))
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom);
|
||||
var coords = _transform.GetMapCoordinates(args.Args.Target.Value);
|
||||
EntityUid popupEnt = default!;
|
||||
foreach (var proto in spawnEntities)
|
||||
|
||||
if (_containerSystem.TryGetContainingContainer(args.Args.Target.Value, out var container))
|
||||
{
|
||||
// distribute the spawned items randomly in a small radius around the origin
|
||||
popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f)));
|
||||
foreach (var proto in spawnEntities)
|
||||
{
|
||||
// distribute the spawned items randomly in a small radius around the origin
|
||||
popupEnt = SpawnInContainerOrDrop(proto, container.Owner, container.ID);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var proto in spawnEntities)
|
||||
{
|
||||
// distribute the spawned items randomly in a small radius around the origin
|
||||
popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f)));
|
||||
}
|
||||
}
|
||||
|
||||
// only show a big popup when butchering living things.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -10,26 +8,11 @@ namespace Content.Shared.Atmos.EntitySystems
|
||||
{
|
||||
public abstract class SharedGasTileOverlaySystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// The temperature at which the heat distortion effect starts to be applied.
|
||||
/// </summary>
|
||||
private float _tempAtMinHeatDistortion;
|
||||
/// <summary>
|
||||
/// The temperature at which the heat distortion effect is at maximum strength.
|
||||
/// </summary>
|
||||
private float _tempAtMaxHeatDistortion;
|
||||
/// <summary>
|
||||
/// Calculated linear slope and intercept to map temperature to a heat distortion strength from 0.0 to 1.0
|
||||
/// </summary>
|
||||
private float _heatDistortionSlope;
|
||||
private float _heatDistortionIntercept;
|
||||
|
||||
public const byte ChunkSize = 8;
|
||||
protected float AccumulatedFrameTime;
|
||||
protected bool PvsEnabled;
|
||||
|
||||
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
|
||||
[Dependency] protected readonly IConfigurationManager ConfMan = default!;
|
||||
|
||||
/// <summary>
|
||||
/// array of the ids of all visible gases.
|
||||
@@ -39,11 +22,6 @@ namespace Content.Shared.Atmos.EntitySystems
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Make sure the heat distortion variables are updated if the CVars change
|
||||
Subs.CVar(ConfMan, CCVars.GasOverlayHeatMinimum, UpdateMinHeat, true);
|
||||
Subs.CVar(ConfMan, CCVars.GasOverlayHeatMaximum, UpdateMaxHeat, true);
|
||||
|
||||
SubscribeLocalEvent<GasTileOverlayComponent, ComponentGetState>(OnGetState);
|
||||
|
||||
List<int> visibleGases = new();
|
||||
@@ -58,29 +36,6 @@ namespace Content.Shared.Atmos.EntitySystems
|
||||
VisibleGasId = visibleGases.ToArray();
|
||||
}
|
||||
|
||||
private void UpdateMaxHeat(float val)
|
||||
{
|
||||
_tempAtMaxHeatDistortion = val;
|
||||
UpdateHeatSlopeAndIntercept();
|
||||
}
|
||||
|
||||
private void UpdateMinHeat(float val)
|
||||
{
|
||||
_tempAtMinHeatDistortion = val;
|
||||
UpdateHeatSlopeAndIntercept();
|
||||
}
|
||||
|
||||
private void UpdateHeatSlopeAndIntercept()
|
||||
{
|
||||
// Make sure to avoid invalid settings (min == max or min > max)
|
||||
// I'm not sure if CVars can have constraints or if CVar subscribers can reject changes.
|
||||
var diff = _tempAtMinHeatDistortion < _tempAtMaxHeatDistortion
|
||||
? _tempAtMaxHeatDistortion - _tempAtMinHeatDistortion
|
||||
: 0.001f;
|
||||
_heatDistortionSlope = 1.0f / diff;
|
||||
_heatDistortionIntercept = -_tempAtMinHeatDistortion * _heatDistortionSlope;
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, GasTileOverlayComponent component, ref ComponentGetState args)
|
||||
{
|
||||
if (PvsEnabled && !args.ReplayState)
|
||||
@@ -117,26 +72,14 @@ namespace Content.Shared.Atmos.EntitySystems
|
||||
[ViewVariables]
|
||||
public readonly byte[] Opacity;
|
||||
|
||||
/// <summary>
|
||||
/// This temperature is currently only used by the GasTileHeatOverlay.
|
||||
/// This value will only reflect the true temperature of the gas when the temperature is between
|
||||
/// <see cref="SharedGasTileOverlaySystem._tempAtMinHeatDistortion"/> and <see cref="SharedGasTileOverlaySystem._tempAtMaxHeatDistortion"/> as these are the only
|
||||
/// values at which the heat distortion varies.
|
||||
/// Additionally, it will only update when the heat distortion strength changes by
|
||||
/// <see cref="_heatDistortionStrengthChangeTolerance"/>. By default, this is 5%, which corresponds to
|
||||
/// 20 steps from <see cref="SharedGasTileOverlaySystem._tempAtMinHeatDistortion"/> to <see cref="SharedGasTileOverlaySystem._tempAtMaxHeatDistortion"/>.
|
||||
/// For 325K to 1000K with 5% tolerance, then this field will dirty only if it differs by 33.75K, or 20 steps.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly float Temperature;
|
||||
|
||||
// TODO change fire color based on temps
|
||||
// But also: dont dirty on a 0.01 kelvin change in temperatures.
|
||||
// Either have a temp tolerance, or map temperature -> byte levels
|
||||
|
||||
public GasOverlayData(byte fireState, byte[] opacity, float temperature)
|
||||
public GasOverlayData(byte fireState, byte[] opacity)
|
||||
{
|
||||
FireState = fireState;
|
||||
Opacity = opacity;
|
||||
Temperature = temperature;
|
||||
}
|
||||
|
||||
public bool Equals(GasOverlayData other)
|
||||
@@ -156,26 +99,10 @@ namespace Content.Shared.Atmos.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
// This is only checking if two datas are equal -- a different routine is used to check if the
|
||||
// temperature differs enough to dirty the chunk using a much wider tolerance.
|
||||
if (!MathHelper.CloseToPercent(Temperature, other.Temperature))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the heat distortion from a temperature.
|
||||
/// Returns 0.0f below TempAtMinHeatDistortion and 1.0f above TempAtMaxHeatDistortion.
|
||||
/// </summary>
|
||||
/// <param name="temp"></param>
|
||||
/// <returns></returns>
|
||||
public float GetHeatDistortionStrength(float temp)
|
||||
{
|
||||
return MathHelper.Clamp01(temp * _heatDistortionSlope + _heatDistortionIntercept);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GasOverlayUpdateEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
@@ -12,23 +12,4 @@ public sealed partial class CCVars
|
||||
|
||||
public static readonly CVarDef<int> GasOverlayThresholds =
|
||||
CVarDef.Create("net.gasoverlaythresholds", 20);
|
||||
|
||||
public static readonly CVarDef<float> GasOverlayHeatThreshold =
|
||||
CVarDef.Create("net.gasoverlayheatthreshold",
|
||||
0.05f,
|
||||
CVar.SERVER | CVar.REPLICATED,
|
||||
"Threshold for sending tile temperature updates to client in percent of distortion strength," +
|
||||
"from 0.0 to 1.0. Example: 0.05 = 5%, which means heat distortion will appear in 20 'steps'.");
|
||||
|
||||
public static readonly CVarDef<float> GasOverlayHeatMinimum =
|
||||
CVarDef.Create("net.gasoverlayheatminimum",
|
||||
325f,
|
||||
CVar.SERVER | CVar.REPLICATED,
|
||||
"Temperature at which heat distortion effect will begin to apply.");
|
||||
|
||||
public static readonly CVarDef<float> GasOverlayHeatMaximum =
|
||||
CVarDef.Create("net.gasoverlayheatmaximum",
|
||||
1000f,
|
||||
CVar.SERVER | CVar.REPLICATED,
|
||||
"Temperature at which heat distortion effect will be at maximum strength.");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -27,7 +28,7 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
|
||||
public Color EyeColor { get; set; } = Color.Black;
|
||||
|
||||
[DataField]
|
||||
public Color SkinColor { get; set; } = Humanoid.SkinColor.ValidHumanSkinTone;
|
||||
public Color SkinColor { get; set; } = Color.FromHsv(new Vector4(0.07f, 0.2f, 1f, 1f));
|
||||
|
||||
[DataField]
|
||||
public List<Marking> Markings { get; set; } = new();
|
||||
@@ -92,14 +93,13 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
|
||||
|
||||
public static HumanoidCharacterAppearance DefaultWithSpecies(string species)
|
||||
{
|
||||
var speciesPrototype = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species);
|
||||
var skinColor = speciesPrototype.SkinColoration switch
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
var speciesPrototype = protoMan.Index<SpeciesPrototype>(species);
|
||||
var skinColoration = protoMan.Index(speciesPrototype.SkinColoration).Strategy;
|
||||
var skinColor = skinColoration.InputType switch
|
||||
{
|
||||
HumanoidSkinColor.HumanToned => Humanoid.SkinColor.HumanSkinTone(speciesPrototype.DefaultHumanSkinTone),
|
||||
HumanoidSkinColor.Hues => speciesPrototype.DefaultSkinTone,
|
||||
HumanoidSkinColor.TintedHues => Humanoid.SkinColor.TintedHues(speciesPrototype.DefaultSkinTone),
|
||||
HumanoidSkinColor.VoxFeathers => Humanoid.SkinColor.ClosestVoxColor(speciesPrototype.DefaultSkinTone),
|
||||
_ => Humanoid.SkinColor.ValidHumanSkinTone,
|
||||
SkinColorationStrategyInput.Unary => skinColoration.FromUnary(speciesPrototype.DefaultHumanSkinTone),
|
||||
SkinColorationStrategyInput.Color => skinColoration.ClosestSkinColor(speciesPrototype.DefaultSkinTone),
|
||||
};
|
||||
|
||||
return new(
|
||||
@@ -147,23 +147,15 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
|
||||
|
||||
var newEyeColor = random.Pick(RealisticEyeColors);
|
||||
|
||||
var skinType = IoCManager.Resolve<IPrototypeManager>().Index<SpeciesPrototype>(species).SkinColoration;
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
var skinType = protoMan.Index<SpeciesPrototype>(species).SkinColoration;
|
||||
var strategy = protoMan.Index(skinType).Strategy;
|
||||
|
||||
var newSkinColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1);
|
||||
switch (skinType)
|
||||
var newSkinColor = strategy.InputType switch
|
||||
{
|
||||
case HumanoidSkinColor.HumanToned:
|
||||
newSkinColor = Humanoid.SkinColor.HumanSkinTone(random.Next(0, 101));
|
||||
break;
|
||||
case HumanoidSkinColor.Hues:
|
||||
break;
|
||||
case HumanoidSkinColor.TintedHues:
|
||||
newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(newSkinColor);
|
||||
break;
|
||||
case HumanoidSkinColor.VoxFeathers:
|
||||
newSkinColor = Humanoid.SkinColor.ProportionalVoxColor(newSkinColor);
|
||||
break;
|
||||
}
|
||||
SkinColorationStrategyInput.Unary => strategy.FromUnary(random.NextFloat(0f, 100f)),
|
||||
SkinColorationStrategyInput.Color => strategy.ClosestSkinColor(new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1)),
|
||||
};
|
||||
|
||||
return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, new ());
|
||||
|
||||
@@ -207,10 +199,8 @@ public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance,
|
||||
markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto);
|
||||
markingSet.EnsureValid(markingManager);
|
||||
|
||||
if (!Humanoid.SkinColor.VerifySkinColor(speciesProto.SkinColoration, skinColor))
|
||||
{
|
||||
skinColor = Humanoid.SkinColor.ValidSkinTone(speciesProto.SkinColoration, skinColor);
|
||||
}
|
||||
var strategy = proto.Index(speciesProto.SkinColoration).Strategy;
|
||||
skinColor = strategy.EnsureVerified(skinColor);
|
||||
|
||||
markingSet.EnsureSpecies(species, skinColor, markingManager);
|
||||
markingSet.EnsureSexes(sex, markingManager);
|
||||
|
||||
@@ -81,7 +81,7 @@ public sealed partial class SpeciesPrototype : IPrototype
|
||||
/// Method of skin coloration used by the species.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public HumanoidSkinColor SkinColoration { get; private set; }
|
||||
public ProtoId<SkinColorationPrototype> SkinColoration { get; private set; }
|
||||
|
||||
[DataField]
|
||||
public ProtoId<LocalizedDatasetPrototype> MaleFirstNames { get; private set; } = "NamesFirstMale";
|
||||
|
||||
@@ -297,9 +297,10 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (verify && !SkinColor.VerifySkinColor(species.SkinColoration, skinColor))
|
||||
if (verify && _proto.Resolve(species.SkinColoration, out var index))
|
||||
{
|
||||
skinColor = SkinColor.ValidSkinTone(species.SkinColoration, skinColor);
|
||||
var strategy = index.Strategy;
|
||||
skinColor = strategy.EnsureVerified(skinColor);
|
||||
}
|
||||
|
||||
humanoid.SkinColor = skinColor;
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.VisualBasic.CompilerServices;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
public static class SkinColor
|
||||
{
|
||||
public const float MaxTintedHuesSaturation = 0.1f;
|
||||
public const float MinTintedHuesLightness = 0.85f;
|
||||
|
||||
public const float MinHuesLightness = 0.175f;
|
||||
|
||||
public const float MinFeathersHue = 29f / 360;
|
||||
public const float MaxFeathersHue = 174f / 360;
|
||||
public const float MinFeathersSaturation = 20f / 100;
|
||||
public const float MaxFeathersSaturation = 88f / 100;
|
||||
public const float MinFeathersValue = 36f / 100;
|
||||
public const float MaxFeathersValue = 55f / 100;
|
||||
|
||||
public static Color ValidHumanSkinTone => Color.FromHsv(new Vector4(0.07f, 0.2f, 1f, 1f));
|
||||
|
||||
/// <summary>
|
||||
/// Turn a color into a valid tinted hue skin tone.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to validate</param>
|
||||
/// <returns>Validated tinted hue skin tone</returns>
|
||||
public static Color ValidTintedHuesSkinTone(Color color)
|
||||
{
|
||||
return TintedHues(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a human skin tone based on a scale of 0 to 100. The value is clamped between 0 and 100.
|
||||
/// </summary>
|
||||
/// <param name="tone">Skin tone. Valid range is 0 to 100, inclusive. 0 is gold/yellowish, 100 is dark brown.</param>
|
||||
/// <returns>A human skin tone.</returns>
|
||||
public static Color HumanSkinTone(int tone)
|
||||
{
|
||||
// 0 - 100, 0 being gold/yellowish and 100 being dark
|
||||
// HSV based
|
||||
//
|
||||
// 0 - 20 changes the hue
|
||||
// 20 - 100 changes the value
|
||||
// 0 is 45 - 20 - 100
|
||||
// 20 is 25 - 20 - 100
|
||||
// 100 is 25 - 100 - 20
|
||||
|
||||
tone = Math.Clamp(tone, 0, 100);
|
||||
|
||||
var rangeOffset = tone - 20;
|
||||
|
||||
float hue = 25;
|
||||
float sat = 20;
|
||||
float val = 100;
|
||||
|
||||
if (rangeOffset <= 0)
|
||||
{
|
||||
hue += Math.Abs(rangeOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
sat += rangeOffset;
|
||||
val -= rangeOffset;
|
||||
}
|
||||
|
||||
var color = Color.FromHsv(new Vector4(hue / 360, sat / 100, val / 100, 1.0f));
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human skin tone from a given color.
|
||||
/// </summary>
|
||||
/// <param name="color"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Does not cause an exception if the color is not originally from the human color range.
|
||||
/// Instead, it will return the approximation of the skin tone value.
|
||||
/// </remarks>
|
||||
public static float HumanSkinToneFromColor(Color color)
|
||||
{
|
||||
var hsv = Color.ToHsv(color);
|
||||
// check for hue/value first, if hue is lower than this percentage
|
||||
// and value is 1.0
|
||||
// then it'll be hue
|
||||
if (Math.Clamp(hsv.X, 25f / 360f, 1) > 25f / 360f
|
||||
&& hsv.Z == 1.0)
|
||||
{
|
||||
return Math.Abs(45 - (hsv.X * 360));
|
||||
}
|
||||
// otherwise it'll directly be the saturation
|
||||
else
|
||||
{
|
||||
return hsv.Y * 100;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify if a color is in the human skin tone range.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to verify</param>
|
||||
/// <returns>True if valid, false otherwise.</returns>
|
||||
public static bool VerifyHumanSkinTone(Color color)
|
||||
{
|
||||
var colorValues = Color.ToHsv(color);
|
||||
|
||||
var hue = Math.Round(colorValues.X * 360f);
|
||||
var sat = Math.Round(colorValues.Y * 100f);
|
||||
var val = Math.Round(colorValues.Z * 100f);
|
||||
// rangeOffset makes it so that this value
|
||||
// is 25 <= hue <= 45
|
||||
if (hue < 25 || hue > 45)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// rangeOffset makes it so that these two values
|
||||
// are 20 <= sat <= 100 and 20 <= val <= 100
|
||||
// where saturation increases to 100 and value decreases to 20
|
||||
if (sat < 20 || val < 20)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a color to the 'tinted hues' skin tone type.
|
||||
/// </summary>
|
||||
/// <param name="color">Color to convert</param>
|
||||
/// <returns>Tinted hue color</returns>
|
||||
public static Color TintedHues(Color color)
|
||||
{
|
||||
var newColor = Color.ToHsl(color);
|
||||
newColor.Y *= MaxTintedHuesSaturation;
|
||||
newColor.Z = MathHelper.Lerp(MinTintedHuesLightness, 1f, newColor.Z);
|
||||
|
||||
return Color.FromHsv(newColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify if this color is a valid tinted hue color type, or not.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to verify</param>
|
||||
/// <returns>True if valid, false otherwise</returns>
|
||||
public static bool VerifyTintedHues(Color color)
|
||||
{
|
||||
// tinted hues just ensures saturation is always .1, or 10% saturation at all times
|
||||
return Color.ToHsl(color).Y <= MaxTintedHuesSaturation && Color.ToHsl(color).Z >= MinTintedHuesLightness;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Color proportionally to the allowed vox color range.
|
||||
/// Will NOT preserve the specific input color even if it is within the allowed vox color range.
|
||||
/// </summary>
|
||||
/// <param name="color">Color to convert</param>
|
||||
/// <returns>Vox feather coloration</returns>
|
||||
public static Color ProportionalVoxColor(Color color)
|
||||
{
|
||||
var newColor = Color.ToHsv(color);
|
||||
|
||||
newColor.X = newColor.X * (MaxFeathersHue - MinFeathersHue) + MinFeathersHue;
|
||||
newColor.Y = newColor.Y * (MaxFeathersSaturation - MinFeathersSaturation) + MinFeathersSaturation;
|
||||
newColor.Z = newColor.Z * (MaxFeathersValue - MinFeathersValue) + MinFeathersValue;
|
||||
|
||||
return Color.FromHsv(newColor);
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Ensures the input Color is within the allowed vox color range.
|
||||
// /// </summary>
|
||||
// /// <param name="color">Color to convert</param>
|
||||
// /// <returns>The same Color if it was within the allowed range, or the closest matching Color otherwise</returns>
|
||||
public static Color ClosestVoxColor(Color color)
|
||||
{
|
||||
var hsv = Color.ToHsv(color);
|
||||
|
||||
hsv.X = Math.Clamp(hsv.X, MinFeathersHue, MaxFeathersHue);
|
||||
hsv.Y = Math.Clamp(hsv.Y, MinFeathersSaturation, MaxFeathersSaturation);
|
||||
hsv.Z = Math.Clamp(hsv.Z, MinFeathersValue, MaxFeathersValue);
|
||||
|
||||
return Color.FromHsv(hsv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify if this color is a valid vox feather coloration, or not.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to verify</param>
|
||||
/// <returns>True if valid, false otherwise</returns>
|
||||
public static bool VerifyVoxFeathers(Color color)
|
||||
{
|
||||
var colorHsv = Color.ToHsv(color);
|
||||
|
||||
if (colorHsv.X < MinFeathersHue || colorHsv.X > MaxFeathersHue)
|
||||
return false;
|
||||
|
||||
if (colorHsv.Y < MinFeathersSaturation || colorHsv.Y > MaxFeathersSaturation)
|
||||
return false;
|
||||
|
||||
if (colorHsv.Z < MinFeathersValue || colorHsv.Z > MaxFeathersValue)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This takes in a color, and returns a color guaranteed to be above MinHuesLightness
|
||||
/// </summary>
|
||||
/// <param name="color"></param>
|
||||
/// <returns>Either the color as-is if it's above MinHuesLightness, or the color with luminosity increased above MinHuesLightness</returns>
|
||||
public static Color MakeHueValid(Color color)
|
||||
{
|
||||
var manipulatedColor = Color.ToHsv(color);
|
||||
manipulatedColor.Z = Math.Max(manipulatedColor.Z, MinHuesLightness);
|
||||
return Color.FromHsv(manipulatedColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify if this color is above a minimum luminosity
|
||||
/// </summary>
|
||||
/// <param name="color"></param>
|
||||
/// <returns>True if valid, false if not</returns>
|
||||
public static bool VerifyHues(Color color)
|
||||
{
|
||||
return Color.ToHsv(color).Z >= MinHuesLightness;
|
||||
}
|
||||
|
||||
public static bool VerifySkinColor(HumanoidSkinColor type, Color color)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
HumanoidSkinColor.HumanToned => VerifyHumanSkinTone(color),
|
||||
HumanoidSkinColor.TintedHues => VerifyTintedHues(color),
|
||||
HumanoidSkinColor.Hues => VerifyHues(color),
|
||||
HumanoidSkinColor.VoxFeathers => VerifyVoxFeathers(color),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public static Color ValidSkinTone(HumanoidSkinColor type, Color color)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
HumanoidSkinColor.HumanToned => ValidHumanSkinTone,
|
||||
HumanoidSkinColor.TintedHues => ValidTintedHuesSkinTone(color),
|
||||
HumanoidSkinColor.Hues => MakeHueValid(color),
|
||||
HumanoidSkinColor.VoxFeathers => ClosestVoxColor(color),
|
||||
_ => color
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum HumanoidSkinColor : byte
|
||||
{
|
||||
HumanToned,
|
||||
Hues,
|
||||
VoxFeathers, // Vox feathers are limited to a specific color range
|
||||
TintedHues, //This gives a color tint to a humanoid's skin (10% saturation with full hue range).
|
||||
}
|
||||
302
Content.Shared/Humanoid/SkinColorationPrototype.cs
Normal file
302
Content.Shared/Humanoid/SkinColorationPrototype.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Humanoid;
|
||||
|
||||
/// <summary>
|
||||
/// A prototype containing a SkinColorationStrategy
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class SkinColorationPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The skin coloration strategy specified by this prototype
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ISkinColorationStrategy Strategy = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of input taken by a <see cref="SkinColorationStrategy" />
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum SkinColorationStrategyInput
|
||||
{
|
||||
/// <summary>
|
||||
/// A single floating point number from 0 to 100 (inclusive)
|
||||
/// </summary>
|
||||
Unary,
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Color" />
|
||||
/// </summary>
|
||||
Color,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes in the given <see cref="SkinColorationStrategyInput" /> and returns an adjusted Color
|
||||
/// </summary>
|
||||
public interface ISkinColorationStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of input expected by the implementor; callers should consult InputType before calling the methods that require a given input
|
||||
/// </summary>
|
||||
SkinColorationStrategyInput InputType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not the provided <see cref="Color" /> is within bounds of this strategy
|
||||
/// </summary>
|
||||
bool VerifySkinColor(Color color);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the closest skin color that this strategy would provide to the given <see cref="Color" />
|
||||
/// </summary>
|
||||
Color ClosestSkinColor(Color color);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the input if it passes <see cref="VerifySkinColor">, otherwise returns <see cref="ClosestSkinColor" />
|
||||
/// </summary>
|
||||
Color EnsureVerified(Color color)
|
||||
{
|
||||
if (VerifySkinColor(color))
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
return ClosestSkinColor(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a colour representation of the given unary input
|
||||
/// </summary>
|
||||
Color FromUnary(float unary)
|
||||
{
|
||||
throw new InvalidOperationException("This coloration strategy does not support unary input");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a colour representation of the given unary input
|
||||
/// </summary>
|
||||
float ToUnary(Color color)
|
||||
{
|
||||
throw new InvalidOperationException("This coloration strategy does not support unary input");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unary coloration strategy that returns human skin tones, with 0 being lightest and 100 being darkest
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class HumanTonedSkinColoration : ISkinColorationStrategy
|
||||
{
|
||||
[DataField]
|
||||
public Color ValidHumanSkinTone = Color.FromHsv(new Vector4(0.07f, 0.2f, 1f, 1f));
|
||||
|
||||
public SkinColorationStrategyInput InputType => SkinColorationStrategyInput.Unary;
|
||||
|
||||
public bool VerifySkinColor(Color color)
|
||||
{
|
||||
var colorValues = Color.ToHsv(color);
|
||||
|
||||
var hue = Math.Round(colorValues.X * 360f);
|
||||
var sat = Math.Round(colorValues.Y * 100f);
|
||||
var val = Math.Round(colorValues.Z * 100f);
|
||||
// rangeOffset makes it so that this value
|
||||
// is 25 <= hue <= 45
|
||||
if (hue < 25f || hue > 45f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// rangeOffset makes it so that these two values
|
||||
// are 20 <= sat <= 100 and 20 <= val <= 100
|
||||
// where saturation increases to 100 and value decreases to 20
|
||||
if (sat < 20f || val < 20f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Color ClosestSkinColor(Color color)
|
||||
{
|
||||
return ValidHumanSkinTone;
|
||||
}
|
||||
|
||||
public Color FromUnary(float color)
|
||||
{
|
||||
// 0 - 100, 0 being gold/yellowish and 100 being dark
|
||||
// HSV based
|
||||
//
|
||||
// 0 - 20 changes the hue
|
||||
// 20 - 100 changes the value
|
||||
// 0 is 45 - 20 - 100
|
||||
// 20 is 25 - 20 - 100
|
||||
// 100 is 25 - 100 - 20
|
||||
|
||||
var tone = Math.Clamp(color, 0f, 100f);
|
||||
|
||||
var rangeOffset = tone - 20f;
|
||||
|
||||
var hue = 25f;
|
||||
var sat = 20f;
|
||||
var val = 100f;
|
||||
|
||||
if (rangeOffset <= 0)
|
||||
{
|
||||
hue += Math.Abs(rangeOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
sat += rangeOffset;
|
||||
val -= rangeOffset;
|
||||
}
|
||||
|
||||
return Color.FromHsv(new Vector4(hue / 360f, sat / 100f, val / 100f, 1.0f));
|
||||
}
|
||||
|
||||
public float ToUnary(Color color)
|
||||
{
|
||||
var hsv = Color.ToHsv(color);
|
||||
// check for hue/value first, if hue is lower than this percentage
|
||||
// and value is 1.0
|
||||
// then it'll be hue
|
||||
if (Math.Clamp(hsv.X, 25f / 360f, 1) > 25f / 360f
|
||||
&& hsv.Z == 1.0)
|
||||
{
|
||||
return Math.Abs(45 - (hsv.X * 360));
|
||||
}
|
||||
// otherwise it'll directly be the saturation
|
||||
else
|
||||
{
|
||||
return hsv.Y * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unary coloration strategy that clamps the color within the HSV colorspace
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ClampedHsvColoration : ISkinColorationStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The (min, max) of the hue channel.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public (float, float)? Hue;
|
||||
|
||||
/// <summary>
|
||||
/// The (min, max) of the saturation channel.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public (float, float)? Saturation;
|
||||
|
||||
/// <summary>
|
||||
/// The (min, max) of the value channel.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public (float, float)? Value;
|
||||
|
||||
public SkinColorationStrategyInput InputType => SkinColorationStrategyInput.Color;
|
||||
|
||||
public bool VerifySkinColor(Color color)
|
||||
{
|
||||
var hsv = Color.ToHsv(color);
|
||||
|
||||
if (Hue is (var minHue, var maxHue) && (hsv.X < minHue || hsv.X > maxHue))
|
||||
return false;
|
||||
|
||||
if (Saturation is (var minSaturation, var maxSaturation) && (hsv.Y < minSaturation || hsv.Y > maxSaturation))
|
||||
return false;
|
||||
|
||||
if (Value is (var minValue, var maxValue) && (hsv.Z < minValue || hsv.Z > maxValue))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Color ClosestSkinColor(Color color)
|
||||
{
|
||||
var hsv = Color.ToHsv(color);
|
||||
|
||||
if (Hue is (var minHue, var maxHue))
|
||||
hsv.X = Math.Clamp(hsv.X, minHue, maxHue);
|
||||
|
||||
if (Saturation is (var minSaturation, var maxSaturation))
|
||||
hsv.Y = Math.Clamp(hsv.Y, minSaturation, maxSaturation);
|
||||
|
||||
if (Value is (var minValue, var maxValue))
|
||||
hsv.Z = Math.Clamp(hsv.Z, minValue, maxValue);
|
||||
|
||||
return Color.FromHsv(hsv);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unary coloration strategy that clamps the color within the HSL colorspace
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ClampedHslColoration : ISkinColorationStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The (min, max) of the hue channel.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public (float, float)? Hue;
|
||||
|
||||
/// <summary>
|
||||
/// The (min, max) of the saturation channel.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public (float, float)? Saturation;
|
||||
|
||||
/// <summary>
|
||||
/// The (min, max) of the lightness channel.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public (float, float)? Lightness;
|
||||
|
||||
public SkinColorationStrategyInput InputType => SkinColorationStrategyInput.Color;
|
||||
|
||||
public bool VerifySkinColor(Color color)
|
||||
{
|
||||
var hsl = Color.ToHsl(color);
|
||||
|
||||
if (Hue is (var minHue, var maxHue) && (hsl.X < minHue || hsl.X > maxHue))
|
||||
return false;
|
||||
|
||||
if (Saturation is (var minSaturation, var maxSaturation) && (hsl.Y < minSaturation || hsl.Y > maxSaturation))
|
||||
return false;
|
||||
|
||||
if (Lightness is (var minValue, var maxValue) && (hsl.Z < minValue || hsl.Z > maxValue))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Color ClosestSkinColor(Color color)
|
||||
{
|
||||
var hsl = Color.ToHsl(color);
|
||||
|
||||
if (Hue is (var minHue, var maxHue))
|
||||
hsl.X = Math.Clamp(hsl.X, minHue, maxHue);
|
||||
|
||||
if (Saturation is (var minSaturation, var maxSaturation))
|
||||
hsl.Y = Math.Clamp(hsl.Y, minSaturation, maxSaturation);
|
||||
|
||||
if (Lightness is (var minValue, var maxValue))
|
||||
hsl.Z = Math.Clamp(hsl.Z, minValue, maxValue);
|
||||
|
||||
return Color.FromHsl(hsl);
|
||||
}
|
||||
}
|
||||
@@ -1303,10 +1303,17 @@ namespace Content.Shared.Interaction
|
||||
var ev = new AccessibleOverrideEvent(user, target);
|
||||
|
||||
RaiseLocalEvent(user, ref ev);
|
||||
RaiseLocalEvent(target, ref ev);
|
||||
|
||||
// If either has handled it and neither has said we can't access it then we can access it.
|
||||
if (ev.Handled)
|
||||
return ev.Accessible;
|
||||
|
||||
return CanAccess(user, target);
|
||||
}
|
||||
|
||||
public bool CanAccess(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container))
|
||||
return true;
|
||||
|
||||
@@ -1511,16 +1518,16 @@ namespace Content.Shared.Interaction
|
||||
/// <summary>
|
||||
/// Override event raised directed on the user to say the target is accessible.
|
||||
/// </summary>
|
||||
/// <param name="User"></param>
|
||||
/// <param name="Target"></param>
|
||||
/// <param name="Target">Entity we're targeting</param>
|
||||
[ByRefEvent]
|
||||
public record struct AccessibleOverrideEvent(EntityUid User, EntityUid Target)
|
||||
{
|
||||
public readonly EntityUid User = User;
|
||||
public readonly EntityUid Target = Target;
|
||||
|
||||
// We set it to true by default for easier validation later.
|
||||
public bool Handled;
|
||||
public bool Accessible = false;
|
||||
public bool Accessible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -32,18 +32,19 @@ namespace Content.Shared.Kitchen;
|
||||
/// </summary>
|
||||
public sealed class SharedKitchenSpikeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _logger = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _logger = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedBodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -76,6 +77,9 @@ public sealed class SharedKitchenSpikeSystem : EntitySystem
|
||||
SubscribeLocalEvent<KitchenSpikeHookedComponent, PickupAttemptEvent>(OnAttempt);
|
||||
SubscribeLocalEvent<KitchenSpikeHookedComponent, IsEquippingAttemptEvent>(OnAttempt);
|
||||
SubscribeLocalEvent<KitchenSpikeHookedComponent, IsUnequippingAttemptEvent>(OnAttempt);
|
||||
|
||||
// Container Jank
|
||||
SubscribeLocalEvent<KitchenSpikeHookedComponent, AccessibleOverrideEvent>(OnAccessibleOverride);
|
||||
}
|
||||
|
||||
private void OnInit(Entity<KitchenSpikeComponent> ent, ref ComponentInit args)
|
||||
@@ -382,6 +386,21 @@ public sealed class SharedKitchenSpikeSystem : EntitySystem
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnAccessibleOverride(Entity<KitchenSpikeHookedComponent> ent, ref AccessibleOverrideEvent args)
|
||||
{
|
||||
// Check if the entity is the target to avoid giving the hooked entity access to everything.
|
||||
// If we already have access we don't need to run more code.
|
||||
if (args.Accessible || args.Target != ent.Owner)
|
||||
return;
|
||||
|
||||
var xform = Transform(ent);
|
||||
if (!_interaction.CanAccess(args.User, xform.ParentUid))
|
||||
return;
|
||||
|
||||
args.Accessible = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
@@ -59,9 +59,6 @@ public enum CollisionGroup
|
||||
TableMask = Impassable | MidImpassable,
|
||||
TableLayer = MidImpassable,
|
||||
|
||||
// Tables that SmallMobs can't go under
|
||||
CounterLayer = MidImpassable | LowImpassable,
|
||||
|
||||
// Tabletop machines, windoors, firelocks
|
||||
TabletopMachineMask = Impassable | HighImpassable,
|
||||
// Tabletop machines
|
||||
|
||||
@@ -145,18 +145,17 @@ public abstract partial class SharedStationAiSystem : EntitySystem
|
||||
|
||||
private void OnAiAccessible(Entity<StationAiOverlayComponent> ent, ref AccessibleOverrideEvent args)
|
||||
{
|
||||
// We don't want to allow entities to access the AI just because the eye is nearby.
|
||||
// Only let the AI access entities through the eye.
|
||||
if (args.Accessible || args.User != ent.Owner)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
// Hopefully AI never needs storage
|
||||
if (_containers.TryGetContainingContainer(args.Target, out var targetContainer))
|
||||
{
|
||||
if (_containers.TryGetContainingContainer(args.Target, out var targetContainer) ||
|
||||
!_containers.IsInSameOrTransparentContainer(ent.Owner, args.Target, otherContainer: targetContainer))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_containers.IsInSameOrTransparentContainer(args.User, args.Target, otherContainer: targetContainer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
args.Accessible = true;
|
||||
}
|
||||
|
||||
@@ -9,16 +9,20 @@ public sealed class SkinTonesTest
|
||||
[Test]
|
||||
public void TestHumanSkinToneValidity()
|
||||
{
|
||||
var strategy = new HumanTonedSkinColoration();
|
||||
|
||||
for (var i = 0; i <= 100; i++)
|
||||
{
|
||||
var color = SkinColor.HumanSkinTone(i);
|
||||
Assert.That(SkinColor.VerifyHumanSkinTone(color));
|
||||
var color = strategy.FromUnary(i);
|
||||
Assert.That(strategy.VerifySkinColor(color));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultSkinToneValid()
|
||||
{
|
||||
Assert.That(SkinColor.VerifyHumanSkinTone(SkinColor.ValidHumanSkinTone));
|
||||
var strategy = new HumanTonedSkinColoration();
|
||||
|
||||
Assert.That(strategy.VerifySkinColor(strategy.ValidHumanSkinTone));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,4 @@
|
||||
mask:
|
||||
- TableMask
|
||||
layer:
|
||||
- CounterLayer
|
||||
- TableLayer
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
mask:
|
||||
- TableMask
|
||||
layer:
|
||||
- CounterLayer
|
||||
- TableLayer
|
||||
- type: Damageable
|
||||
damageContainer: StructuralInorganic
|
||||
damageModifierSet: Wood
|
||||
@@ -201,7 +201,7 @@
|
||||
|
||||
- type: entity
|
||||
id: TableReinforced
|
||||
parent: CounterBase
|
||||
parent: TableBase
|
||||
name: reinforced table
|
||||
description: A square piece of metal standing on four metal legs. Extra robust.
|
||||
components:
|
||||
@@ -471,7 +471,7 @@
|
||||
|
||||
- type: entity
|
||||
id: TableBrass
|
||||
parent: CounterBase
|
||||
parent: TableBase
|
||||
name: brass table
|
||||
description: A shiny, corrosion resistant brass table. Steampunk!
|
||||
components:
|
||||
@@ -615,7 +615,7 @@
|
||||
|
||||
- type: entity
|
||||
id: TableStone
|
||||
parent: CounterBase
|
||||
parent: TableBase
|
||||
name: stone table
|
||||
description: Literally the sturdiest thing you have ever seen.
|
||||
components:
|
||||
@@ -675,7 +675,7 @@
|
||||
collection: FootstepCarpet
|
||||
|
||||
- type: entity
|
||||
parent: CounterBase
|
||||
parent: TableBase
|
||||
id: TableXeno
|
||||
name: xeno table
|
||||
description: I wouldn't put the silverware on it.
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
mask:
|
||||
- TableMask
|
||||
layer:
|
||||
- CounterLayer
|
||||
- TableLayer
|
||||
- type: TimedDespawn
|
||||
lifetime: 180
|
||||
- type: PointLight
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
- 0.50,0.50
|
||||
- -0.50,0.50
|
||||
layer:
|
||||
- ConveyorMask
|
||||
- Impassable
|
||||
- MidImpassable
|
||||
- LowImpassable
|
||||
- DoorPassable
|
||||
hard: False
|
||||
- type: Conveyor
|
||||
- type: DeviceNetwork
|
||||
|
||||
@@ -115,13 +115,3 @@
|
||||
id: Hologram
|
||||
kind: source
|
||||
path: "/Textures/Shaders/hologram.swsl"
|
||||
|
||||
- type: shader
|
||||
id: Heat
|
||||
kind: source
|
||||
path: "/Textures/Shaders/heat.swsl"
|
||||
params:
|
||||
spatial_scale: 1.0
|
||||
strength_scale: 1.0
|
||||
speed_scale: 1.0
|
||||
grid_ent_from_viewport_local: 1,0,0,1,0,1
|
||||
|
||||
27
Resources/Prototypes/Species/skin_colorations.yml
Normal file
27
Resources/Prototypes/Species/skin_colorations.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
- type: skinColoration
|
||||
id: Hues
|
||||
strategy: !type:ClampedHsvColoration
|
||||
value: [0.175, 1]
|
||||
|
||||
- type: skinColoration
|
||||
id: TintedHues
|
||||
strategy: !type:ClampedHslColoration
|
||||
saturation: [0, 0.1]
|
||||
lightness: [0.85, 1]
|
||||
|
||||
- type: skinColoration
|
||||
id: VoxFeathers
|
||||
strategy: !type:ClampedHsvColoration
|
||||
hue: [0.081, 0.48]
|
||||
saturation: [0.2, 0.8]
|
||||
value: [0.36, 0.55]
|
||||
|
||||
- type: skinColoration
|
||||
id: HumanToned
|
||||
strategy: !type:HumanTonedSkinColoration {}
|
||||
|
||||
- type: skinColoration
|
||||
id: VulpkaninColors
|
||||
strategy: !type:ClampedHslColoration
|
||||
saturation: [0.0, 0.60]
|
||||
lightness: [0.2, 0.9]
|
||||
@@ -7,7 +7,7 @@
|
||||
defaultSkinTone: "#5a3f2d"
|
||||
markingLimits: MobVulpkaninMarkingLimits
|
||||
dollPrototype: MobVulpkaninDummy
|
||||
skinColoration: Hues # TODO: Introduce clamping once #39175 or a similiar PR is merged. Ideally lower max saturation to around 80% and some minimum brightness. Same for markings.
|
||||
skinColoration: VulpkaninColors
|
||||
maleFirstNames: names_vulpkanin_male
|
||||
femaleFirstNames: names_vulpkanin_female
|
||||
lastNames: names_vulpkanin_last
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
uniform sampler2D SCREEN_TEXTURE;
|
||||
|
||||
// Number of frequencies to combine, can't be a parameter/uniform else it causes problems in compatibility mode
|
||||
// I have no idea why
|
||||
const highp int N = 32;
|
||||
|
||||
uniform highp float spatial_scale; // spatial scaling of modes, higher = fine turbulence, lower = coarse turbulence
|
||||
uniform highp float strength_scale; // distortion strength
|
||||
uniform highp float speed_scale; // scaling factor on the speed of the animation
|
||||
// Matrix to convert screen coordinates into grid coordinates
|
||||
// This is to "pin" the effect to the grid, so that it does not shimmer as you move
|
||||
uniform highp mat3 grid_ent_from_viewport_local;
|
||||
|
||||
const highp float TWO_PI = 6.28318530718;
|
||||
// This is just the default target values so that the external parameters can be normalized to 1
|
||||
const highp float strength_factor = 0.0005;
|
||||
const highp float spatial_factor = 22.0;
|
||||
|
||||
// 1D pseudo-random function
|
||||
highp float random_1d(highp float n) {
|
||||
return fract(sin(n * 12.9898) * 43758.5453);
|
||||
}
|
||||
|
||||
// Kolmogorov amplitude, power spectrum goes as k^(–11/6)
|
||||
highp float kolAmp(highp float k) {
|
||||
return pow(k, -11.0 / 6.0);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
|
||||
highp vec2 ps = vec2(1.0/SCREEN_PIXEL_SIZE.x, 1.0/SCREEN_PIXEL_SIZE.y);
|
||||
highp float aspectratio = ps.x / ps.y;
|
||||
|
||||
// scale the scale factor with the number of modes just cuz it works reasonably
|
||||
highp float s_scale = spatial_scale * spatial_factor / sqrt(float(N));
|
||||
|
||||
// Coordinates to use to calculate the effects, convert to grid coordinates
|
||||
highp vec2 uvW = (grid_ent_from_viewport_local * vec3(UV.x, UV.y, 1.0)).xy;
|
||||
// Scale the coordinates
|
||||
uvW *= s_scale;
|
||||
|
||||
// accumulate phase gradienta
|
||||
highp vec2 grad = vec2(0.0);
|
||||
|
||||
for (lowp int i = 0; i < N; i++) {
|
||||
// float cast of the index
|
||||
highp float fi = float(i);
|
||||
|
||||
// Pick a random direction
|
||||
highp float ang = random_1d(fi + 1.0) * TWO_PI;
|
||||
highp vec2 dir = vec2(cos(ang), sin(ang));
|
||||
|
||||
// Pick a random spatial frequency from 0.5 to 30
|
||||
highp float k = mix(0.5, 30.0, random_1d(fi + 17.0));
|
||||
|
||||
// Pick a random speed from 0.05 to 0.20
|
||||
highp float speed = mix(3., 8., random_1d(fi + 33.0));
|
||||
|
||||
// Pick a random phase offset
|
||||
highp float phi_0 = random_1d(fi + 49.0) * TWO_PI;
|
||||
|
||||
// phase argument
|
||||
highp float t = dot(dir, uvW) * k + TIME * speed * speed_scale + phi_0;
|
||||
|
||||
// analytical gradient: ∇[sin(t)] = cos(t) * ∇t
|
||||
// ∇t = k * dir * scale (scale is factored out)
|
||||
grad += kolAmp(k) * cos(t) * k * dir;
|
||||
}
|
||||
// Spatial scaling (coarse or fine turbulence)
|
||||
grad *= s_scale;
|
||||
|
||||
// The texture should have been blurred using a previous operation
|
||||
// We use the alpha channel to cut off the blur that bleeds outside the tile, then we rescale
|
||||
// the mask back up to 0.0 to 1.0
|
||||
highp float mask = clamp((zTexture(UV).a - 0.5)*2.0, 0.00, 1.0);
|
||||
|
||||
// Calculate warped UV using the turbulence gradient
|
||||
// The strength of the turbulence is encoded into the red channel of TEXTURE
|
||||
// Give it a little polynomial boost: https://www.wolframalpha.com/input?i=-x%5E2+%2B2x+from+0+to+1
|
||||
highp float heatStrength = zTexture(UV).r*1.0;
|
||||
heatStrength = clamp(-heatStrength*heatStrength + 2.0*heatStrength, 0.0, 1.0);
|
||||
highp vec2 uvDist = UV + (strength_scale * strength_factor * heatStrength * mask) * grad;
|
||||
|
||||
// Apply to the texture
|
||||
COLOR = texture2D(SCREEN_TEXTURE, uvDist);
|
||||
|
||||
// Uncomment the following two lines to view the strength buffer directly
|
||||
// COLOR.rgb = vec3(heatStrength * mask);
|
||||
// COLOR.a = mask;
|
||||
}
|
||||
Reference in New Issue
Block a user