Merge branch 'lazers-and-hop-console' into great-merge
This commit is contained in:
@@ -42,7 +42,9 @@ namespace Content.Client.Access.UI
|
||||
};
|
||||
|
||||
_window.CrewManifestButton.OnPressed += _ => SendMessage(new CrewManifestOpenUiMessage());
|
||||
_window.PrivilegedIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
|
||||
_window.PrivilegedIdButton.OnPressed +=
|
||||
_ => SendMessage(new ItemSlotButtonPressedEvent(PrivilegedIdCardSlotId));
|
||||
|
||||
_window.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(TargetIdCardSlotId));
|
||||
|
||||
_window.OnClose += Close;
|
||||
@@ -65,7 +67,12 @@ namespace Content.Client.Access.UI
|
||||
_window?.UpdateState(castState);
|
||||
}
|
||||
|
||||
public void SubmitData(string newFullName, string newJobTitle, List<string> newAccessList, string newJobPrototype)
|
||||
public void SubmitData(
|
||||
string newFullName,
|
||||
string newJobTitle,
|
||||
List<string> newAccessList,
|
||||
string newJobPrototype,
|
||||
string? newJobIcon)
|
||||
{
|
||||
if (newFullName.Length > MaxFullNameLength)
|
||||
newFullName = newFullName[..MaxFullNameLength];
|
||||
@@ -77,7 +84,8 @@ namespace Content.Client.Access.UI
|
||||
newFullName,
|
||||
newJobTitle,
|
||||
newAccessList,
|
||||
newJobPrototype));
|
||||
newJobPrototype,
|
||||
newJobIcon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
MinSize="650 290">
|
||||
MinSize="650 290">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<GridContainer Columns="2">
|
||||
<GridContainer Columns="3" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'id-card-console-window-privileged-id'}" />
|
||||
<Button Name="PrivilegedIdButton" Access="Public"/>
|
||||
<Button Name="PrivilegedIdButton" Access="Public" />
|
||||
<Label Name="PrivilegedIdLabel" />
|
||||
|
||||
<Label Text="{Loc 'id-card-console-window-target-id'}" />
|
||||
<Button Name="TargetIdButton" Access="Public"/>
|
||||
<Button Name="TargetIdButton" Access="Public" />
|
||||
<Label Name="TargetIdLabel" />
|
||||
</GridContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
@@ -35,5 +35,13 @@
|
||||
<!-- Access level buttons are added here by the C# code -->
|
||||
|
||||
</GridContainer>
|
||||
<!-- WD EDIT -->
|
||||
<GridContainer Name="CurrentJobIcon" Columns="2">
|
||||
<Label Text="Текущая выбранная иконка для роли: " />
|
||||
</GridContainer>
|
||||
<GridContainer Name="JobIconsGrid" Columns="10" HorizontalAlignment="Center">
|
||||
<!-- Job icon buttons are generated in the code -->
|
||||
</GridContainer>
|
||||
<!-- WD EDIT END -->
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||
|
||||
namespace Content.Client.Access.UI
|
||||
@@ -16,18 +20,24 @@ namespace Content.Client.Access.UI
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!; //WD-EDIT
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!; //WD-EDIT
|
||||
private readonly ISawmill _logMill = default!;
|
||||
|
||||
private readonly IdCardConsoleBoundUserInterface _owner;
|
||||
|
||||
private readonly Dictionary<string, Button> _accessButtons = new();
|
||||
private readonly Dictionary<string, TextureButton> _jobIconButtons = new(); //WD-EDIT
|
||||
private readonly List<string> _jobPrototypeIds = new();
|
||||
|
||||
private string? _lastFullName;
|
||||
private string? _lastJobTitle;
|
||||
private string? _lastJobProto;
|
||||
private string? _lastJobIcon; //WD-EDIT
|
||||
|
||||
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
|
||||
public IdCardConsoleWindow(
|
||||
IdCardConsoleBoundUserInterface owner,
|
||||
IPrototypeManager prototypeManager,
|
||||
List<ProtoId<AccessLevelPrototype>> accessLevels)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -41,6 +51,7 @@ namespace Content.Client.Access.UI
|
||||
{
|
||||
FullNameSaveButton.Disabled = FullNameSaveButton.Text == _lastFullName;
|
||||
};
|
||||
|
||||
FullNameSaveButton.OnPressed += _ => SubmitData();
|
||||
|
||||
JobTitleLineEdit.OnTextEntered += _ => SubmitData();
|
||||
@@ -48,6 +59,7 @@ namespace Content.Client.Access.UI
|
||||
{
|
||||
JobTitleSaveButton.Disabled = JobTitleLineEdit.Text == _lastJobTitle;
|
||||
};
|
||||
|
||||
JobTitleSaveButton.OnPressed += _ => SubmitData();
|
||||
|
||||
var jobs = _prototypeManager.EnumeratePrototypes<JobPrototype>().ToList();
|
||||
@@ -69,7 +81,7 @@ namespace Content.Client.Access.UI
|
||||
|
||||
foreach (var access in accessLevels)
|
||||
{
|
||||
if (!prototypeManager.TryIndex<AccessLevelPrototype>(access, out var accessLevel))
|
||||
if (!prototypeManager.TryIndex(access, out var accessLevel))
|
||||
{
|
||||
_logMill.Error($"Unable to find accesslevel for {access}");
|
||||
continue;
|
||||
@@ -78,19 +90,50 @@ namespace Content.Client.Access.UI
|
||||
var newButton = new Button
|
||||
{
|
||||
Text = GetAccessLevelName(accessLevel),
|
||||
ToggleMode = true,
|
||||
ToggleMode = true
|
||||
};
|
||||
|
||||
_accessButtons.Add(accessLevel.ID, newButton);
|
||||
newButton.OnPressed += _ => SubmitData();
|
||||
buttonsToAdd.Add(newButton);
|
||||
}
|
||||
|
||||
buttonsToAdd.Sort((x,y) => string.Compare(x.Text, y.Text, StringComparison.Ordinal));
|
||||
buttonsToAdd.Sort((x, y) => string.Compare(x.Text, y.Text, StringComparison.Ordinal));
|
||||
|
||||
foreach (var button in buttonsToAdd)
|
||||
{
|
||||
AccessLevelGrid.AddChild(button);
|
||||
}
|
||||
|
||||
//WD-EDIT
|
||||
if (!_entityManager.TryGetComponent<IdCardConsoleComponent>(owner.Owner, out var idConsoleComponent))
|
||||
return;
|
||||
|
||||
var path = new ResPath("/Textures/Interface/Misc/job_icons.rsi");
|
||||
_resource.TryGetResource(path, out RSIResource? rsi);
|
||||
if (rsi == null)
|
||||
return;
|
||||
|
||||
foreach (var jobIcon in idConsoleComponent.JobIcons)
|
||||
{
|
||||
var newButton = new TextureButton
|
||||
{
|
||||
TextureNormal = rsi.RSI.TryGetState(jobIcon, out var state) ? state.Frame0
|
||||
: rsi.RSI.TryGetState("CustomId", out var customState) ? customState.Frame0
|
||||
: null,
|
||||
Scale = new Vector2(5, 5)
|
||||
};
|
||||
|
||||
_jobIconButtons.Add(jobIcon, newButton);
|
||||
newButton.OnPressed += _ =>
|
||||
{
|
||||
_lastJobIcon = jobIcon;
|
||||
SubmitData();
|
||||
};
|
||||
|
||||
JobIconsGrid.AddChild(newButton);
|
||||
}
|
||||
//WD-EDIT
|
||||
}
|
||||
|
||||
private static string GetAccessLevelName(AccessLevelPrototype prototype)
|
||||
@@ -149,6 +192,7 @@ namespace Content.Client.Access.UI
|
||||
}
|
||||
}
|
||||
|
||||
_lastJobIcon = job.Icon;
|
||||
SubmitData();
|
||||
}
|
||||
|
||||
@@ -195,11 +239,11 @@ namespace Content.Client.Access.UI
|
||||
foreach (var (accessName, button) in _accessButtons)
|
||||
{
|
||||
button.Disabled = !interfaceEnabled;
|
||||
if (interfaceEnabled)
|
||||
{
|
||||
button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
|
||||
button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
|
||||
}
|
||||
if (!interfaceEnabled)
|
||||
continue;
|
||||
|
||||
button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
|
||||
button.Disabled = !state.AllowedModifyAccessList?.Contains(accessName) ?? true;
|
||||
}
|
||||
|
||||
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
|
||||
@@ -208,6 +252,37 @@ namespace Content.Client.Access.UI
|
||||
JobPresetOptionButton.SelectId(jobIndex);
|
||||
}
|
||||
|
||||
//WD-EDIT
|
||||
if (_resource.TryGetResource(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), out RSIResource? rsi))
|
||||
{
|
||||
CurrentJobIcon.RemoveAllChildren();
|
||||
var newLabel = new Label
|
||||
{
|
||||
Text = "Текущая выбранная иконка для роли: "
|
||||
};
|
||||
|
||||
CurrentJobIcon.AddChild(newLabel);
|
||||
var newIcon = new TextureRect
|
||||
{
|
||||
Texture = rsi.RSI.TryGetState(state.TargetIdJobIcon?.Replace("JobIcon", ""), out var iconState)
|
||||
? iconState.Frame0
|
||||
: rsi.RSI.TryGetState("CustomId", out var customState)
|
||||
? customState.Frame0
|
||||
: null,
|
||||
|
||||
TextureScale = new Vector2(4, 4)
|
||||
};
|
||||
|
||||
CurrentJobIcon.AddChild(newIcon);
|
||||
}
|
||||
|
||||
foreach (var (jobIcon, button) in _jobIconButtons)
|
||||
{
|
||||
button.Disabled = !interfaceEnabled;
|
||||
button.Pressed = state.TargetIdJobIcon == jobIcon;
|
||||
}
|
||||
//WD-EDIT
|
||||
|
||||
_lastFullName = state.TargetIdFullName;
|
||||
_lastJobTitle = state.TargetIdJobTitle;
|
||||
_lastJobProto = state.TargetIdJobPrototype;
|
||||
@@ -217,14 +292,15 @@ namespace Content.Client.Access.UI
|
||||
{
|
||||
// Don't send this if it isn't dirty.
|
||||
var jobProtoDirty = _lastJobProto != null &&
|
||||
_jobPrototypeIds[JobPresetOptionButton.SelectedId] != _lastJobProto;
|
||||
_jobPrototypeIds[JobPresetOptionButton.SelectedId] != _lastJobProto;
|
||||
|
||||
_owner.SubmitData(
|
||||
FullNameLineEdit.Text,
|
||||
JobTitleLineEdit.Text,
|
||||
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
||||
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
|
||||
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
|
||||
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty,
|
||||
_lastJobIcon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Spawners\" />
|
||||
<Folder Include="White\Trail\" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />
|
||||
|
||||
@@ -21,6 +21,7 @@ using Content.Client.White.JoinQueue;
|
||||
using Content.Client.White.Jukebox;
|
||||
using Content.Client.White.Sponsors;
|
||||
using Content.Client.White.Stalin;
|
||||
using Content.Client.White.Trail.Line.Manager;
|
||||
using Content.Client.White.TTS;
|
||||
using Content.Shared.Administration.Managers;
|
||||
|
||||
@@ -57,6 +58,7 @@ namespace Content.Client.IoC
|
||||
IoCManager.Register<StalinManager>();
|
||||
IoCManager.Register<ClientJukeboxSongsSyncManager>();
|
||||
IoCManager.Register<TTSManager>();
|
||||
IoCManager.Register<ITrailLineManager, TrailSplineManager>();
|
||||
//WD-EDIT
|
||||
}
|
||||
}
|
||||
|
||||
15
Content.Client/White/Trail/Line/ITrailLine.cs
Normal file
15
Content.Client/White/Trail/Line/ITrailLine.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.White.Trail.Line;
|
||||
|
||||
public interface ITrailLine
|
||||
{
|
||||
ITrailSettings Settings { get; }
|
||||
|
||||
void TryCreateSegment((Vector2 WorldPosition, Angle WorldRotation) worldPosRot, MapId mapId);
|
||||
|
||||
void Render(DrawingHandleWorld handle, Texture? texture);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Content.Client.White.Trail.Line.Manager;
|
||||
|
||||
public interface ITrailLineHolder
|
||||
{
|
||||
public ITrailLine? TrailLine { get; set; }
|
||||
}
|
||||
15
Content.Client/White/Trail/Line/Manager/ITrailLineManager.cs
Normal file
15
Content.Client/White/Trail/Line/Manager/ITrailLineManager.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.White.Trail.Line.Manager;
|
||||
|
||||
public interface ITrailLineManager
|
||||
{
|
||||
IEnumerable<ITrailLine> Lines { get; }
|
||||
|
||||
ITrailLine CreateTrail(ITrailSettings settings, MapId mapId);
|
||||
|
||||
void Detach(ITrailLineHolder holder);
|
||||
|
||||
void Update(float dt);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Content.Client.White.Trail.SplineRenderer;
|
||||
using Content.Shared.White.Spline;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.White.Trail.Line.Manager;
|
||||
|
||||
public sealed class TrailSplineManager : ITrailLineManager
|
||||
{
|
||||
private readonly LinkedList<TrailSpline> _lines = new();
|
||||
|
||||
public IEnumerable<ITrailLine> Lines => _lines;
|
||||
|
||||
public ITrailLine CreateTrail(ITrailSettings settings, MapId mapId)
|
||||
{
|
||||
var tline = new TrailSpline
|
||||
{
|
||||
Attached = true,
|
||||
Settings = settings,
|
||||
MapId = mapId,
|
||||
SplineIterator = Spline.From2DType(settings.SplineIteratorType),
|
||||
GradientIterator = Spline.From4DType(settings.GradientIteratorType),
|
||||
Renderer = TrailSplineRenderer.FromType(settings.SplineRendererType)
|
||||
};
|
||||
|
||||
_lines.AddLast(tline);
|
||||
return tline;
|
||||
}
|
||||
|
||||
public void Detach(ITrailLineHolder holder)
|
||||
{
|
||||
if (holder.TrailLine is TrailSpline trailSpline)
|
||||
{
|
||||
trailSpline.Attached = false;
|
||||
var detachedSettings = new TrailSettings();
|
||||
TrailSettings.Inject(detachedSettings, trailSpline.Settings);
|
||||
trailSpline.Settings = detachedSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float dt)
|
||||
{
|
||||
var curNode = _lines.First;
|
||||
while (curNode != null)
|
||||
{
|
||||
var curLine = curNode.Value;
|
||||
curNode = curNode.Next;
|
||||
|
||||
if (!curLine.HasSegments())
|
||||
{
|
||||
if (curLine.Attached)
|
||||
curLine.ResetLifetime();
|
||||
else
|
||||
_lines.Remove(curLine);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
curLine.AddLifetime(dt);
|
||||
curLine.RemoveExpiredSegments();
|
||||
curLine.UpdateSegments(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
Content.Client/White/Trail/Line/TrailSpline.cs
Normal file
183
Content.Client/White/Trail/Line/TrailSpline.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using Content.Client.White.Trail.SplineRenderer;
|
||||
using Content.Shared.White.Spline;
|
||||
using Content.Shared.White.Spline.Linear;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Content.Client.White.Trail.Line;
|
||||
|
||||
public sealed class TrailSpline : ITrailLine
|
||||
{
|
||||
private static readonly IRobustRandom Random = IoCManager.Resolve<IRobustRandom>();
|
||||
|
||||
[ViewVariables]
|
||||
private readonly LinkedList<TrailSplineSegment> _segments = new();
|
||||
|
||||
[ViewVariables]
|
||||
private Vector2 _lastCreationPos;
|
||||
|
||||
[ViewVariables]
|
||||
private float _curLifetime;
|
||||
|
||||
[ViewVariables]
|
||||
private Vector2? _virtualSegmentPos;
|
||||
|
||||
[ViewVariables]
|
||||
public MapId MapId { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public bool Attached { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public ITrailSettings Settings { get; set; } = TrailSettings.Default;
|
||||
|
||||
[ViewVariables]
|
||||
public ISpline<Vector2> SplineIterator { get; set; } = new SplineLinear2D();
|
||||
|
||||
[ViewVariables]
|
||||
public ISpline<Vector4> GradientIterator { get; set; } = new SplineLinear4D();
|
||||
|
||||
[ViewVariables]
|
||||
public ITrailSplineRenderer Renderer { get; set; } = new TrailSplineRendererDebug();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasSegments()
|
||||
{
|
||||
return _segments.Count > 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddLifetime(float time)
|
||||
{
|
||||
_curLifetime += time;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ResetLifetime()
|
||||
{
|
||||
_curLifetime = 0f;
|
||||
}
|
||||
|
||||
public void TryCreateSegment((Vector2 WorldPosition, Angle WorldRotation) worldPosRot, MapId mapId)
|
||||
{
|
||||
if (!Attached)
|
||||
return;
|
||||
|
||||
if (mapId != MapId)
|
||||
return;
|
||||
|
||||
if (worldPosRot.WorldPosition == Vector2.Zero)
|
||||
return;
|
||||
|
||||
var pos = worldPosRot.WorldPosition + worldPosRot.WorldRotation.RotateVec(Settings.CreationOffset);
|
||||
|
||||
_lastCreationPos = pos;
|
||||
|
||||
if (_virtualSegmentPos.HasValue)
|
||||
{
|
||||
var vPos = _virtualSegmentPos.Value;
|
||||
if ((vPos - pos).LengthSquared() > Settings.СreationDistanceThresholdSquared)
|
||||
{
|
||||
_segments.AddLast(new TrailSplineSegment()
|
||||
{ Position = vPos, ExistTil = _curLifetime + Settings.Lifetime });
|
||||
|
||||
_virtualSegmentPos = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var lastPos = _segments.Last?.Value.Position;
|
||||
if (!lastPos.HasValue || (lastPos.Value - pos).LengthSquared() > Settings.СreationDistanceThresholdSquared)
|
||||
_virtualSegmentPos = pos;
|
||||
}
|
||||
|
||||
public void UpdateSegments(float dt)
|
||||
{
|
||||
var gravity = Settings.Gravity;
|
||||
var maxRandomWalk = Settings.MaxRandomWalk;
|
||||
var lifetime = Settings.Lifetime;
|
||||
|
||||
if (_segments.Last != null)
|
||||
{
|
||||
var i = 0;
|
||||
var positions = new Vector2[_segments.Count + 1];
|
||||
positions[_segments.Count] = _lastCreationPos;
|
||||
|
||||
var curNode = _segments.First;
|
||||
while (curNode != null)
|
||||
{
|
||||
var offset = gravity;
|
||||
var curValue = curNode.Value;
|
||||
if (maxRandomWalk != Vector2.Zero)
|
||||
{
|
||||
positions[i] = curValue.Position;
|
||||
if (curNode.Next != null)
|
||||
{
|
||||
positions[i + 1] = curNode.Next.Value.Position;
|
||||
if (curNode.Next.Next != null)
|
||||
positions[i + 2] = curNode.Next.Next.Value.Position;
|
||||
}
|
||||
|
||||
var effectiveRandomWalk = maxRandomWalk * (curValue.ExistTil - _curLifetime) / lifetime;
|
||||
var gradientNorm = -SplineIterator.SampleVelocity(positions, i).Normalized();
|
||||
offset += gradientNorm * effectiveRandomWalk.Y * Random.NextFloat(-1.0f, 1.0f);
|
||||
var rotated90Degrees = new Vector2(-gradientNorm.Y, gradientNorm.X);
|
||||
offset += rotated90Degrees * effectiveRandomWalk.X * Random.NextFloat(-1.0f, 1.0f);
|
||||
}
|
||||
|
||||
curValue.Position += offset;
|
||||
i++;
|
||||
curNode = curNode.Next;
|
||||
}
|
||||
}
|
||||
|
||||
if (_virtualSegmentPos.HasValue)
|
||||
_virtualSegmentPos = _virtualSegmentPos.Value + gravity;
|
||||
|
||||
if (!Attached)
|
||||
_lastCreationPos += gravity;
|
||||
}
|
||||
|
||||
public void RemoveExpiredSegments()
|
||||
{
|
||||
while (_segments.First?.Value.ExistTil < _curLifetime)
|
||||
{
|
||||
_segments.RemoveFirst();
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(DrawingHandleWorld handle, Texture? texture)
|
||||
{
|
||||
if (_segments.Last == null)
|
||||
return;
|
||||
|
||||
var arrSize = _segments.Count + 1;
|
||||
var paPositions = new Vector2[arrSize];
|
||||
var paLifetimes = new float[arrSize];
|
||||
paPositions[0] = _lastCreationPos;
|
||||
paLifetimes[0] = 1f;
|
||||
|
||||
var reversedIndexedSegments = _segments.Reverse().Select((x, i) => (x, i + 1));
|
||||
foreach (var (x, i) in reversedIndexedSegments)
|
||||
{
|
||||
paPositions[i] = x.Position;
|
||||
paLifetimes[i] = (x.ExistTil - _curLifetime) / Settings.Lifetime;
|
||||
}
|
||||
|
||||
Renderer.Render(handle, texture, SplineIterator, GradientIterator, Settings, paPositions, paLifetimes);
|
||||
}
|
||||
|
||||
private sealed class TrailSplineSegment
|
||||
{
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public float ExistTil { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.White.Spline;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Client.Graphics;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Content.Client.White.Trail.SplineRenderer;
|
||||
|
||||
public interface ITrailSplineRenderer
|
||||
{
|
||||
void Render(
|
||||
DrawingHandleWorld handle,
|
||||
Texture? texture,
|
||||
ISpline<Vector2> splineIterator,
|
||||
ISpline<Vector4> gradientIterator,
|
||||
ITrailSettings settings,
|
||||
Vector2[] paPositions,
|
||||
float[] paLifetimes
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Content.Shared.White.Trail;
|
||||
|
||||
namespace Content.Client.White.Trail.SplineRenderer;
|
||||
|
||||
public static class TrailSplineRenderer
|
||||
{
|
||||
public static ITrailSplineRenderer FromType(TrailSplineRendererType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
TrailSplineRendererType.Continuous => new TrailSplineRendererContinuous(),
|
||||
TrailSplineRendererType.Point => new TrailSplineRendererPoint(),
|
||||
TrailSplineRendererType.Debug => new TrailSplineRendererDebug(),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using Content.Shared.White.Spline;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Client.Graphics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Content.Client.White.Trail.SplineRenderer;
|
||||
|
||||
public sealed class TrailSplineRendererContinuous : ITrailSplineRenderer
|
||||
{
|
||||
public void Render(
|
||||
DrawingHandleWorld handle,
|
||||
Texture? texture,
|
||||
ISpline<Vector2> splineIterator,
|
||||
ISpline<Vector4> gradientIterator,
|
||||
ITrailSettings settings,
|
||||
Vector2[] paPositions,
|
||||
float[] paLifetimes
|
||||
)
|
||||
{
|
||||
float[] splinePointParams;
|
||||
if (settings.LengthStep == 0f)
|
||||
{
|
||||
splinePointParams = Enumerable.Range(0, paPositions.Length - 1).Select(x => (float) x).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
splinePointParams = splineIterator
|
||||
.IteratePointParamsByLength(paPositions, Math.Max(settings.LengthStep, 0.1f)).ToArray();
|
||||
}
|
||||
|
||||
var gradientControlGroups = gradientIterator.GetControlGroupAmount(settings.Gradient.Length);
|
||||
var colorToPointMul = 0f;
|
||||
if (gradientControlGroups > 0)
|
||||
colorToPointMul = gradientControlGroups / splineIterator.GetControlGroupAmount(paPositions.Length);
|
||||
|
||||
(Vector2, Vector2)? prevPoints = null;
|
||||
foreach (var u in splinePointParams)
|
||||
{
|
||||
var (position, velocity) = splineIterator.SamplePositionVelocity(paPositions, u);
|
||||
|
||||
var offset = new Vector2(-velocity.Y, velocity.X).Normalized() *
|
||||
settings.Scale.X; // 90-degree anticlockwise rotation
|
||||
|
||||
var curPoints = (position - offset, position + offset);
|
||||
|
||||
if (prevPoints.HasValue)
|
||||
{
|
||||
var colorVec = Vector4.One;
|
||||
if (settings.Gradient != null && settings.Gradient.Length > 0)
|
||||
{
|
||||
if (gradientControlGroups > 0)
|
||||
colorVec = gradientIterator.SamplePosition(settings.Gradient, u * colorToPointMul);
|
||||
else
|
||||
colorVec = settings.Gradient[0];
|
||||
}
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
var verts = new DrawVertexUV2D[]
|
||||
{
|
||||
new(curPoints.Item1, Vector2.Zero),
|
||||
new(curPoints.Item2, Vector2.UnitY),
|
||||
new(prevPoints.Value.Item2, Vector2.One),
|
||||
new(prevPoints.Value.Item1, Vector2.UnitX),
|
||||
};
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, texture, verts,
|
||||
new Color(colorVec.X, colorVec.Y, colorVec.Z, colorVec.W));
|
||||
}
|
||||
else
|
||||
{
|
||||
var verts = new[]
|
||||
{
|
||||
curPoints.Item1,
|
||||
curPoints.Item2,
|
||||
prevPoints.Value.Item2,
|
||||
prevPoints.Value.Item1,
|
||||
};
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts,
|
||||
new Color(colorVec.X, colorVec.Y, colorVec.Z, colorVec.W));
|
||||
}
|
||||
}
|
||||
|
||||
prevPoints = curPoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Content.Shared.White.Spline;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Client.Graphics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Content.Client.White.Trail.SplineRenderer;
|
||||
|
||||
public sealed class TrailSplineRendererDebug : ITrailSplineRenderer
|
||||
{
|
||||
public void Render(
|
||||
DrawingHandleWorld handle,
|
||||
Texture? texture,
|
||||
ISpline<Vector2> splineIterator,
|
||||
ISpline<Vector4> gradientIterator,
|
||||
ITrailSettings settings,
|
||||
Vector2[] paPositions,
|
||||
float[] paLifetimes
|
||||
)
|
||||
{
|
||||
float[] splinePointParams;
|
||||
if (settings.LengthStep == 0f)
|
||||
{
|
||||
splinePointParams = Enumerable.Range(0, paPositions.Length - 1).Select(x => (float) x).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
splinePointParams = splineIterator
|
||||
.IteratePointParamsByLength(paPositions, Math.Max(settings.LengthStep, 0.1f)).ToArray();
|
||||
}
|
||||
|
||||
Vector2? prevPosControlPoint = null;
|
||||
foreach (var item in paPositions)
|
||||
{
|
||||
if (prevPosControlPoint.HasValue)
|
||||
handle.DrawLine(item, prevPosControlPoint.Value, Color.Blue);
|
||||
|
||||
prevPosControlPoint = item;
|
||||
}
|
||||
|
||||
Vector2? prevPosSplinePoint = null;
|
||||
foreach (var u in splinePointParams)
|
||||
{
|
||||
var (position, velocity) = splineIterator.SamplePositionVelocity(paPositions, u);
|
||||
if (prevPosSplinePoint.HasValue)
|
||||
handle.DrawLine(position, prevPosSplinePoint.Value, Color.Red);
|
||||
|
||||
handle.DrawLine(position, position + velocity, Color.White);
|
||||
handle.DrawCircle(position, 0.03f, new Color(0, 255, 0));
|
||||
prevPosSplinePoint = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Content.Shared.White.Spline;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Client.Graphics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Content.Client.White.Trail.SplineRenderer;
|
||||
|
||||
public sealed class TrailSplineRendererPoint : ITrailSplineRenderer
|
||||
{
|
||||
public void Render(
|
||||
DrawingHandleWorld handle,
|
||||
Texture? texture,
|
||||
ISpline<Vector2> splineIterator,
|
||||
ISpline<Vector4> gradientIterator,
|
||||
ITrailSettings settings,
|
||||
Vector2[] paPositions,
|
||||
float[] paLifetimes
|
||||
)
|
||||
{
|
||||
if (texture == null)
|
||||
return;
|
||||
|
||||
float[] splinePointParams;
|
||||
if (settings.LengthStep == 0f)
|
||||
{
|
||||
splinePointParams = Enumerable.Range(0, paPositions.Length - 1).Select(x => (float) x).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
splinePointParams = splineIterator
|
||||
.IteratePointParamsByLength(paPositions, Math.Max(settings.LengthStep, 0.1f)).ToArray();
|
||||
}
|
||||
|
||||
var gradientControlGroups = gradientIterator.GetControlGroupAmount(settings.Gradient.Length);
|
||||
var colorToPointMul = 0f;
|
||||
if (gradientControlGroups > 0)
|
||||
colorToPointMul = gradientControlGroups / gradientIterator.GetControlGroupAmount(paPositions.Length);
|
||||
|
||||
foreach (var u in splinePointParams)
|
||||
{
|
||||
var (position, velocity) = splineIterator.SamplePositionVelocity(paPositions, u);
|
||||
|
||||
var colorVec = Vector4.One;
|
||||
if (settings.Gradient != null && settings.Gradient.Length > 0)
|
||||
{
|
||||
colorVec = gradientControlGroups > 0
|
||||
? gradientIterator.SamplePosition(settings.Gradient, u * colorToPointMul)
|
||||
: settings.Gradient[0];
|
||||
}
|
||||
|
||||
var quad = Box2.FromDimensions(position, texture.Size * settings.Scale / EyeManager.PixelsPerMeter);
|
||||
handle.DrawTextureRect(texture, new Box2Rotated(quad, velocity.ToAngle(), quad.Center),
|
||||
new Color(colorVec.X, colorVec.Y, colorVec.Z, colorVec.W));
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Content.Client/White/Trail/TrailComponent.cs
Normal file
56
Content.Client/White/Trail/TrailComponent.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Content.Client.White.Trail.Line;
|
||||
using Content.Client.White.Trail.Line.Manager;
|
||||
using Content.Client.White.Trail.SplineRenderer;
|
||||
using Content.Shared.White.Spline;
|
||||
using Content.Shared.White.Trail;
|
||||
|
||||
namespace Content.Client.White.Trail;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class TrailComponent : SharedTrailComponent, ITrailLineHolder
|
||||
{
|
||||
[ViewVariables]
|
||||
public ITrailLine? TrailLine { get; set; }
|
||||
|
||||
public override Spline2DType SplineIteratorType
|
||||
{
|
||||
get => base.SplineIteratorType;
|
||||
set
|
||||
{
|
||||
if (base.SplineIteratorType == value)
|
||||
return;
|
||||
|
||||
base.SplineIteratorType = value;
|
||||
if (TrailLine is TrailSpline trailSpline)
|
||||
trailSpline.SplineIterator = Spline.From2DType(value);
|
||||
}
|
||||
}
|
||||
|
||||
public override Spline4DType GradientIteratorType
|
||||
{
|
||||
get => base.GradientIteratorType;
|
||||
set
|
||||
{
|
||||
if (base.GradientIteratorType == value)
|
||||
return;
|
||||
|
||||
base.GradientIteratorType = value;
|
||||
if (TrailLine is TrailSpline trailSpline)
|
||||
trailSpline.GradientIterator = Spline.From4DType(value);
|
||||
}
|
||||
}
|
||||
|
||||
public override TrailSplineRendererType SplineRendererType
|
||||
{
|
||||
get => base.SplineRendererType;
|
||||
set
|
||||
{
|
||||
if (base.SplineRendererType == value)
|
||||
return;
|
||||
|
||||
base.SplineRendererType = value;
|
||||
if (TrailLine is TrailSpline trailSpline)
|
||||
trailSpline.Renderer = TrailSplineRenderer.FromType(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Content.Client/White/Trail/TrailOverlay.cs
Normal file
69
Content.Client/White/Trail/TrailOverlay.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Content.Client.White.Trail.Line.Manager;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.White.Trail;
|
||||
|
||||
public sealed class TrailOverlay : Overlay
|
||||
{
|
||||
private readonly IPrototypeManager _protoManager;
|
||||
private readonly IResourceCache _cache;
|
||||
private readonly ITrailLineManager _lineManager;
|
||||
|
||||
private readonly Dictionary<string, ShaderInstance?> _shaderDict;
|
||||
private readonly Dictionary<string, Texture?> _textureDict;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
|
||||
|
||||
public TrailOverlay(
|
||||
IPrototypeManager protoManager,
|
||||
IResourceCache cache,
|
||||
ITrailLineManager lineManager
|
||||
)
|
||||
{
|
||||
_protoManager = protoManager;
|
||||
_cache = cache;
|
||||
_lineManager = lineManager;
|
||||
|
||||
_shaderDict = new Dictionary<string, ShaderInstance?>();
|
||||
_textureDict = new Dictionary<string, Texture?>();
|
||||
|
||||
ZIndex = (int) Shared.DrawDepth.DrawDepth.Effects;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
foreach (var item in _lineManager.Lines)
|
||||
{
|
||||
item.Render(handle, GetCachedTexture(item.Settings.TexurePath ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
//влепить на ети два метода мемори кеш со слайдинг експирейшоном вместо дикта если проблемы будут
|
||||
private ShaderInstance? GetCachedShader(string id)
|
||||
{
|
||||
if (_shaderDict.TryGetValue(id, out var shader))
|
||||
return shader;
|
||||
|
||||
if (_protoManager.TryIndex<ShaderPrototype>(id, out var shaderRes))
|
||||
shader = shaderRes?.InstanceUnique();
|
||||
|
||||
_shaderDict.Add(id, shader);
|
||||
return shader;
|
||||
}
|
||||
|
||||
private Texture? GetCachedTexture(string path)
|
||||
{
|
||||
if (_textureDict.TryGetValue(path, out var texture))
|
||||
return texture;
|
||||
|
||||
if (_cache.TryGetResource<TextureResource>(path, out var texRes))
|
||||
texture = texRes;
|
||||
|
||||
_textureDict.Add(path, texture);
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
76
Content.Client/White/Trail/TrailSystem.cs
Normal file
76
Content.Client/White/Trail/TrailSystem.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Content.Client.White.Trail.Line.Manager;
|
||||
using Content.Shared.White.Trail;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.White.Trail;
|
||||
|
||||
public sealed class TrailSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly ITrailLineManager _lineManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(
|
||||
new TrailOverlay(
|
||||
IoCManager.Resolve<IPrototypeManager>(),
|
||||
IoCManager.Resolve<IResourceCache>(),
|
||||
_lineManager
|
||||
));
|
||||
|
||||
SubscribeLocalEvent<TrailComponent, MoveEvent>(OnTrailMove);
|
||||
SubscribeLocalEvent<TrailComponent, ComponentRemove>(OnTrailRemove);
|
||||
SubscribeLocalEvent<TrailComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, TrailComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not TrailComponentState state)
|
||||
return;
|
||||
|
||||
TrailSettings.Inject(component, state.Settings);
|
||||
}
|
||||
|
||||
private void OnTrailRemove(EntityUid uid, TrailComponent comp, ComponentRemove args)
|
||||
{
|
||||
_lineManager.Detach(comp);
|
||||
}
|
||||
|
||||
private void OnTrailMove(EntityUid uid, TrailComponent comp, ref MoveEvent args)
|
||||
{
|
||||
if (comp.СreationMethod != SegmentCreationMethod.OnMove || _gameTiming.InPrediction)
|
||||
return;
|
||||
|
||||
TryCreateSegment(comp, args.Component);
|
||||
}
|
||||
|
||||
private void TryCreateSegment(TrailComponent comp, TransformComponent xform)
|
||||
{
|
||||
if (xform.MapID == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
comp.TrailLine ??= _lineManager.CreateTrail(comp, xform.MapID);
|
||||
comp.TrailLine.TryCreateSegment(_transformSystem.GetWorldPositionRotation(xform), xform.MapID);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
_lineManager.Update(frameTime);
|
||||
|
||||
foreach (var (comp, xform) in EntityQuery<TrailComponent, TransformComponent>())
|
||||
{
|
||||
if (comp.СreationMethod == SegmentCreationMethod.OnFrameUpdate)
|
||||
TryCreateSegment(comp, xform);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user