Move do_afters to an overlay (#10463)
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using Content.Client.DoAfter;
|
||||||
using Content.Client.DoAfter.UI;
|
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
|
|
||||||
namespace Content.Client.CharacterInfo
|
namespace Content.Client.CharacterInfo
|
||||||
{
|
{
|
||||||
@@ -14,7 +12,7 @@ namespace Content.Client.CharacterInfo
|
|||||||
{
|
{
|
||||||
var dims = Texture != null ? GetDrawDimensions(Texture) : UIBox2.FromDimensions(Vector2.Zero, PixelSize);
|
var dims = Texture != null ? GetDrawDimensions(Texture) : UIBox2.FromDimensions(Vector2.Zero, PixelSize);
|
||||||
dims.Top = Math.Max(dims.Bottom - dims.Bottom * Progress,0);
|
dims.Top = Math.Max(dims.Bottom - dims.Bottom * Progress,0);
|
||||||
handle.DrawRect(dims, DoAfterHelpers.GetProgressColor(Progress));
|
handle.DrawRect(dims, DoAfterOverlay.GetProgressColor(Progress));
|
||||||
|
|
||||||
base.Draw(handle);
|
base.Draw(handle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Content.Client.DoAfter.UI;
|
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
|
|
||||||
namespace Content.Client.DoAfter
|
namespace Content.Client.DoAfter
|
||||||
@@ -8,8 +7,6 @@ namespace Content.Client.DoAfter
|
|||||||
{
|
{
|
||||||
public readonly Dictionary<byte, ClientDoAfter> DoAfters = new();
|
public readonly Dictionary<byte, ClientDoAfter> DoAfters = new();
|
||||||
|
|
||||||
public readonly List<(TimeSpan CancelTime, ClientDoAfter Message)> CancelledDoAfters = new();
|
public readonly Dictionary<byte, ClientDoAfter> CancelledDoAfters = new();
|
||||||
|
|
||||||
public DoAfterGui? Gui { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
141
Content.Client/DoAfter/DoAfterOverlay.cs
Normal file
141
Content.Client/DoAfter/DoAfterOverlay.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
using Content.Client.Resources;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Shared;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Client.DoAfter;
|
||||||
|
|
||||||
|
public sealed class DoAfterOverlay : Overlay
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
private readonly SharedTransformSystem _transform;
|
||||||
|
|
||||||
|
private Texture _barTexture;
|
||||||
|
private ShaderInstance _shader;
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||||
|
|
||||||
|
public DoAfterOverlay()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
_transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
|
||||||
|
_barTexture = IoCManager.Resolve<IResourceCache>()
|
||||||
|
.GetTexture("/Textures/Interface/Misc/progress_bar.rsi/icon.png");
|
||||||
|
|
||||||
|
_shader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("unshaded").Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var handle = args.WorldHandle;
|
||||||
|
var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
|
||||||
|
var spriteQuery = _entManager.GetEntityQuery<SpriteComponent>();
|
||||||
|
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
|
var scale = _configManager.GetCVar(CVars.DisplayUIScale);
|
||||||
|
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
|
||||||
|
var rotationMatrix = Matrix3.CreateRotation(-rotation);
|
||||||
|
handle.UseShader(_shader);
|
||||||
|
|
||||||
|
// TODO: Need active DoAfter component (or alternatively just make DoAfter itself active)
|
||||||
|
foreach (var comp in _entManager.EntityQuery<DoAfterComponent>(true))
|
||||||
|
{
|
||||||
|
if (comp.DoAfters.Count == 0 ||
|
||||||
|
!xformQuery.TryGetComponent(comp.Owner, out var xform) ||
|
||||||
|
xform.MapID != args.MapId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var worldPosition = _transform.GetWorldPosition(xform);
|
||||||
|
|
||||||
|
if (!args.WorldAABB.Contains(worldPosition))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
var worldMatrix = Matrix3.CreateTranslation(worldPosition);
|
||||||
|
|
||||||
|
foreach (var (_, doAfter) in comp.DoAfters)
|
||||||
|
{
|
||||||
|
var elapsed = doAfter.Accumulator;
|
||||||
|
var displayRatio = MathF.Min(1.0f,
|
||||||
|
elapsed / doAfter.Delay);
|
||||||
|
|
||||||
|
Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld);
|
||||||
|
Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty);
|
||||||
|
|
||||||
|
handle.SetTransform(matty);
|
||||||
|
var offset = _barTexture.Height / scale * index;
|
||||||
|
|
||||||
|
// Use the sprite itself if we know its bounds. This means short or tall sprites don't get overlapped
|
||||||
|
// by the bar.
|
||||||
|
float yOffset;
|
||||||
|
if (spriteQuery.TryGetComponent(comp.Owner, out var sprite))
|
||||||
|
{
|
||||||
|
yOffset = sprite.Bounds.Height / 2f + 0.05f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yOffset = 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position above the entity (we've already applied the matrix transform to the entity itself)
|
||||||
|
// Offset by the texture size for every do_after we have.
|
||||||
|
var position = new Vector2(-_barTexture.Width / 2f / EyeManager.PixelsPerMeter,
|
||||||
|
yOffset / scale + offset / EyeManager.PixelsPerMeter * scale);
|
||||||
|
|
||||||
|
// Draw the underlying bar texture
|
||||||
|
handle.DrawTexture(_barTexture, position);
|
||||||
|
|
||||||
|
// Draw the bar itself
|
||||||
|
var cancelled = doAfter.Cancelled;
|
||||||
|
Color color;
|
||||||
|
const float flashTime = 0.125f;
|
||||||
|
|
||||||
|
// if we're cancelled then flick red / off.
|
||||||
|
if (cancelled)
|
||||||
|
{
|
||||||
|
var flash = Math.Floor(doAfter.CancelledAccumulator / flashTime) % 2 == 0;
|
||||||
|
color = new Color(1f, 0f, 0f, flash ? 1f : 0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
color = GetProgressColor(displayRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardcoded width of the progress bar because it doesn't match the texture.
|
||||||
|
const float startX = 2f;
|
||||||
|
const float endX = 22f;
|
||||||
|
|
||||||
|
var xProgress = (endX - startX) * displayRatio + startX;
|
||||||
|
|
||||||
|
var box = new Box2(new Vector2(startX, 3f) / EyeManager.PixelsPerMeter, new Vector2(xProgress, 4f) / EyeManager.PixelsPerMeter);
|
||||||
|
box = box.Translated(position);
|
||||||
|
handle.DrawRect(box, color);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.UseShader(null);
|
||||||
|
handle.SetTransform(Matrix3.Identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color GetProgressColor(float progress)
|
||||||
|
{
|
||||||
|
if (progress >= 1.0f)
|
||||||
|
{
|
||||||
|
return new Color(0f, 1f, 0f);
|
||||||
|
}
|
||||||
|
// lerp
|
||||||
|
var hue = (5f / 18f) * progress;
|
||||||
|
return Color.FromHsv((hue, 1f, 0.75f, 1f));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Client.DoAfter.UI;
|
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
@@ -20,18 +18,8 @@ namespace Content.Client.DoAfter
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class DoAfterSystem : EntitySystem
|
public sealed class DoAfterSystem : EntitySystem
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* How this is currently setup (client-side):
|
|
||||||
* DoAfterGui handles the actual bars displayed above heads. It also uses FrameUpdate to flash cancellations
|
|
||||||
* DoAfterEntitySystem handles checking predictions every tick as well as removing / cancelling DoAfters due to time elapsed.
|
|
||||||
* DoAfterComponent handles network messages inbound as well as storing the DoAfter data.
|
|
||||||
* It'll also handle overall cleanup when one is removed (i.e. removing it from DoAfterGui).
|
|
||||||
*/
|
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
[Dependency] private readonly IPlayerManager _player = default!;
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
|
||||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// We'll use an excess time so stuff like finishing effects can show.
|
/// We'll use an excess time so stuff like finishing effects can show.
|
||||||
@@ -43,9 +31,14 @@ namespace Content.Client.DoAfter
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
UpdatesOutsidePrediction = true;
|
UpdatesOutsidePrediction = true;
|
||||||
SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
|
SubscribeNetworkEvent<CancelledDoAfterMessage>(OnCancelledDoAfter);
|
||||||
SubscribeLocalEvent<DoAfterComponent, ComponentStartup>(OnDoAfterStartup);
|
|
||||||
SubscribeLocalEvent<DoAfterComponent, ComponentShutdown>(OnDoAfterShutdown);
|
|
||||||
SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
|
SubscribeLocalEvent<DoAfterComponent, ComponentHandleState>(OnDoAfterHandleState);
|
||||||
|
IoCManager.Resolve<IOverlayManager>().AddOverlay(new DoAfterOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
IoCManager.Resolve<IOverlayManager>().RemoveOverlay<DoAfterOverlay>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
|
private void OnDoAfterHandleState(EntityUid uid, DoAfterComponent component, ref ComponentHandleState args)
|
||||||
@@ -86,24 +79,6 @@ namespace Content.Client.DoAfter
|
|||||||
|
|
||||||
component.DoAfters.Add(doAfter.ID, doAfter);
|
component.DoAfters.Add(doAfter.ID, doAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.Gui == null || component.Gui.Disposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (_, doAfter) in component.DoAfters)
|
|
||||||
{
|
|
||||||
component.Gui.AddDoAfter(doAfter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoAfterStartup(EntityUid uid, DoAfterComponent component, ComponentStartup args)
|
|
||||||
{
|
|
||||||
Enable(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDoAfterShutdown(EntityUid uid, DoAfterComponent component, ComponentShutdown args)
|
|
||||||
{
|
|
||||||
Disable(component);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
|
private void OnCancelledDoAfter(CancelledDoAfterMessage ev)
|
||||||
@@ -113,33 +88,6 @@ namespace Content.Client.DoAfter
|
|||||||
Cancel(doAfter, ev.ID);
|
Cancel(doAfter, ev.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For handling PVS so we dispose of controls if they go out of range
|
|
||||||
/// </summary>
|
|
||||||
public void Enable(DoAfterComponent component)
|
|
||||||
{
|
|
||||||
if (component.Gui?.Disposed == false)
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.Gui = new DoAfterGui {AttachedEntity = component.Owner};
|
|
||||||
|
|
||||||
foreach (var (_, doAfter) in component.DoAfters)
|
|
||||||
{
|
|
||||||
component.Gui.AddDoAfter(doAfter);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (_, cancelled) in component.CancelledDoAfters)
|
|
||||||
{
|
|
||||||
component.Gui.CancelDoAfter(cancelled.ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disable(DoAfterComponent component)
|
|
||||||
{
|
|
||||||
component.Gui?.Dispose();
|
|
||||||
component.Gui = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a DoAfter without showing a cancellation graphic.
|
/// Remove a DoAfter without showing a cancellation graphic.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -150,22 +98,10 @@ namespace Content.Client.DoAfter
|
|||||||
|
|
||||||
var found = false;
|
var found = false;
|
||||||
|
|
||||||
for (var i = component.CancelledDoAfters.Count - 1; i >= 0; i--)
|
component.CancelledDoAfters.Remove(clientDoAfter.ID);
|
||||||
{
|
|
||||||
var cancelled = component.CancelledDoAfters[i];
|
|
||||||
|
|
||||||
if (cancelled.Message == clientDoAfter)
|
|
||||||
{
|
|
||||||
component.CancelledDoAfters.RemoveAt(i);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
if (!found)
|
||||||
component.DoAfters.Remove(clientDoAfter.ID);
|
component.DoAfters.Remove(clientDoAfter.ID);
|
||||||
|
|
||||||
component.Gui?.RemoveDoAfter(clientDoAfter.ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -174,98 +110,70 @@ namespace Content.Client.DoAfter
|
|||||||
/// Actual removal is handled by DoAfterEntitySystem.
|
/// Actual removal is handled by DoAfterEntitySystem.
|
||||||
public void Cancel(DoAfterComponent component, byte id)
|
public void Cancel(DoAfterComponent component, byte id)
|
||||||
{
|
{
|
||||||
foreach (var (_, cancelled) in component.CancelledDoAfters)
|
if (component.CancelledDoAfters.ContainsKey(id))
|
||||||
{
|
|
||||||
if (cancelled.ID == id)
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (!component.DoAfters.ContainsKey(id))
|
if (!component.DoAfters.ContainsKey(id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var doAfterMessage = component.DoAfters[id];
|
var doAfterMessage = component.DoAfters[id];
|
||||||
component.CancelledDoAfters.Add((_gameTiming.CurTime, doAfterMessage));
|
doAfterMessage.Cancelled = true;
|
||||||
component.Gui?.CancelDoAfter(id);
|
component.CancelledDoAfters.Add(id, doAfterMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move this to an overlay
|
|
||||||
// TODO separate DoAfter & ActiveDoAfter components for the entity query.
|
// TODO separate DoAfter & ActiveDoAfter components for the entity query.
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
base.Update(frameTime);
|
if (!_gameTiming.IsFirstTimePredicted)
|
||||||
|
|
||||||
var currentTime = _gameTiming.CurTime;
|
|
||||||
var attached = _player.LocalPlayer?.ControlledEntity;
|
|
||||||
|
|
||||||
// Can't see any I guess?
|
|
||||||
if (attached == null || Deleted(attached))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// ReSharper disable once ConvertToLocalFunction
|
var playerEntity = _player.LocalPlayer?.ControlledEntity;
|
||||||
var predicate = static (EntityUid uid, (EntityUid compOwner, EntityUid? attachedEntity) data)
|
|
||||||
=> uid == data.compOwner || uid == data.attachedEntity;
|
|
||||||
|
|
||||||
var occluded = _examineSystem.IsOccluded(attached.Value);
|
foreach (var (comp, xform) in EntityQuery<DoAfterComponent, TransformComponent>())
|
||||||
var viewbox = _eyeManager.GetWorldViewport().Enlarged(2.0f);
|
|
||||||
var xforms = GetEntityQuery<TransformComponent>();
|
|
||||||
var entXform = xforms.GetComponent(attached.Value);
|
|
||||||
var playerPos = _xformSystem.GetWorldPosition(entXform, xforms);
|
|
||||||
|
|
||||||
foreach (var (comp, xform) in EntityManager.EntityQuery<DoAfterComponent, TransformComponent>(true))
|
|
||||||
{
|
{
|
||||||
var doAfters = comp.DoAfters;
|
var doAfters = comp.DoAfters;
|
||||||
|
|
||||||
if (doAfters.Count == 0 || xform.MapID != entXform.MapID)
|
if (doAfters.Count == 0)
|
||||||
{
|
{
|
||||||
Disable(comp);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var compPos = _xformSystem.GetWorldPosition(xform, xforms);
|
|
||||||
|
|
||||||
if (!viewbox.Contains(compPos))
|
|
||||||
{
|
|
||||||
Disable(comp);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = (compPos - playerPos).Length + 0.01f;
|
|
||||||
|
|
||||||
if (occluded &&
|
|
||||||
comp.Owner != attached &&
|
|
||||||
// Static ExamineSystemShared.InRangeUnOccluded has to die.
|
|
||||||
!ExamineSystemShared.InRangeUnOccluded(
|
|
||||||
new(playerPos, entXform.MapID),
|
|
||||||
new(compPos, entXform.MapID), range,
|
|
||||||
(comp.Owner, attached), predicate,
|
|
||||||
entMan: EntityManager))
|
|
||||||
{
|
|
||||||
Disable(comp);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Enable(comp);
|
|
||||||
|
|
||||||
var userGrid = xform.Coordinates;
|
var userGrid = xform.Coordinates;
|
||||||
var toRemove = new RemQueue<ClientDoAfter>();
|
var toRemove = new RemQueue<ClientDoAfter>();
|
||||||
|
|
||||||
// Check cancellations / finishes
|
// Check cancellations / finishes
|
||||||
foreach (var (id, doAfter) in doAfters)
|
foreach (var (id, doAfter) in doAfters)
|
||||||
{
|
{
|
||||||
var elapsedTime = (currentTime - doAfter.StartTime).TotalSeconds;
|
|
||||||
|
|
||||||
// If we've passed the final time (after the excess to show completion graphic) then remove.
|
// If we've passed the final time (after the excess to show completion graphic) then remove.
|
||||||
if (elapsedTime > doAfter.Delay + ExcessTime)
|
if ((doAfter.Accumulator + doAfter.CancelledAccumulator) > doAfter.Delay + ExcessTime)
|
||||||
{
|
{
|
||||||
toRemove.Add(doAfter);
|
toRemove.Add(doAfter);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't predict cancellation if it's already finished.
|
if (doAfter.Cancelled)
|
||||||
if (elapsedTime > doAfter.Delay)
|
{
|
||||||
|
doAfter.CancelledAccumulator += frameTime;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
doAfter.Accumulator += frameTime;
|
||||||
|
|
||||||
|
// Well we finished so don't try to predict cancels.
|
||||||
|
if (doAfter.Accumulator > doAfter.Delay)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Predictions
|
// Predictions
|
||||||
|
if (comp.Owner != playerEntity)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// TODO: Add these back in when I work out some system for changing the accumulation rate
|
||||||
|
// based on ping. Right now these would show as cancelled near completion if we moved at the end
|
||||||
|
// despite succeeding.
|
||||||
|
continue;
|
||||||
|
|
||||||
if (doAfter.BreakOnUserMove)
|
if (doAfter.BreakOnUserMove)
|
||||||
{
|
{
|
||||||
if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold))
|
if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold))
|
||||||
@@ -292,16 +200,21 @@ namespace Content.Client.DoAfter
|
|||||||
Remove(comp, doAfter);
|
Remove(comp, doAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = comp.CancelledDoAfters.Count;
|
|
||||||
// Remove cancelled DoAfters after ExcessTime has elapsed
|
// Remove cancelled DoAfters after ExcessTime has elapsed
|
||||||
for (var i = count - 1; i >= 0; i--)
|
var toRemoveCancelled = new List<ClientDoAfter>();
|
||||||
|
|
||||||
|
foreach (var (_, doAfter) in comp.CancelledDoAfters)
|
||||||
{
|
{
|
||||||
var cancelled = comp.CancelledDoAfters[i];
|
if (doAfter.CancelledAccumulator > ExcessTime)
|
||||||
if ((currentTime - cancelled.CancelTime).TotalSeconds > ExcessTime)
|
|
||||||
{
|
{
|
||||||
Remove(comp, cancelled.Message);
|
toRemoveCancelled.Add(doAfter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var doAfter in toRemoveCancelled)
|
||||||
|
{
|
||||||
|
Remove(comp, doAfter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
|
|
||||||
namespace Content.Client.DoAfter.UI
|
|
||||||
{
|
|
||||||
public sealed class DoAfterBar : Control
|
|
||||||
{
|
|
||||||
private IGameTiming _gameTiming = default!;
|
|
||||||
|
|
||||||
private readonly ShaderInstance _shader;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set from 0.0f to 1.0f to reflect bar progress
|
|
||||||
/// </summary>
|
|
||||||
public float Ratio
|
|
||||||
{
|
|
||||||
get => _ratio;
|
|
||||||
set => _ratio = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float _ratio = 1.0f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flash red until removed
|
|
||||||
/// </summary>
|
|
||||||
public bool Cancelled
|
|
||||||
{
|
|
||||||
get => _cancelled;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_cancelled == value)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cancelled = value;
|
|
||||||
if (_cancelled)
|
|
||||||
{
|
|
||||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
|
||||||
_lastFlash = _gameTiming.CurTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _cancelled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is the cancellation bar red?
|
|
||||||
/// </summary>
|
|
||||||
private bool _flash = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Last time we swapped the flash.
|
|
||||||
/// </summary>
|
|
||||||
private TimeSpan _lastFlash;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How long each cancellation bar flash lasts in seconds.
|
|
||||||
/// </summary>
|
|
||||||
private const float FlashTime = 0.125f;
|
|
||||||
|
|
||||||
private const int XPixelDiff = 20 * DoAfterBarScale;
|
|
||||||
|
|
||||||
public const byte DoAfterBarScale = 2;
|
|
||||||
|
|
||||||
public DoAfterBar()
|
|
||||||
{
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
_shader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("unshaded").Instance();
|
|
||||||
VerticalAlignment = VAlignment.Center;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
|
||||||
{
|
|
||||||
base.FrameUpdate(args);
|
|
||||||
if (Cancelled)
|
|
||||||
{
|
|
||||||
if ((_gameTiming.CurTime - _lastFlash).TotalSeconds > FlashTime)
|
|
||||||
{
|
|
||||||
_lastFlash = _gameTiming.CurTime;
|
|
||||||
_flash = !_flash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Draw(DrawingHandleScreen handle)
|
|
||||||
{
|
|
||||||
base.Draw(handle);
|
|
||||||
|
|
||||||
Color color;
|
|
||||||
|
|
||||||
if (Cancelled)
|
|
||||||
{
|
|
||||||
if ((_gameTiming.CurTime - _lastFlash).TotalSeconds > FlashTime)
|
|
||||||
{
|
|
||||||
_lastFlash = _gameTiming.CurTime;
|
|
||||||
_flash = !_flash;
|
|
||||||
}
|
|
||||||
|
|
||||||
color = new Color(1.0f, 0.0f, 0.0f, _flash ? 1.0f : 0.0f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
color = DoAfterHelpers.GetProgressColor(Ratio);
|
|
||||||
}
|
|
||||||
|
|
||||||
handle.UseShader(_shader);
|
|
||||||
// If you want to make this less hard-coded be my guest
|
|
||||||
var leftOffset = 2 * DoAfterBarScale;
|
|
||||||
var box = new UIBox2i(
|
|
||||||
leftOffset,
|
|
||||||
-2 + 2 * DoAfterBarScale,
|
|
||||||
leftOffset + (int) (XPixelDiff * Ratio * UIScale),
|
|
||||||
-2);
|
|
||||||
handle.DrawRect(box, color);
|
|
||||||
handle.UseShader(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DoAfterHelpers
|
|
||||||
{
|
|
||||||
public static Color GetProgressColor(float progress)
|
|
||||||
{
|
|
||||||
if (progress >= 1.0f)
|
|
||||||
{
|
|
||||||
return new Color(0f, 1f, 0f);
|
|
||||||
}
|
|
||||||
// lerp
|
|
||||||
var hue = (5f / 18f) * progress;
|
|
||||||
return Color.FromHsv((hue, 1f, 0.75f, 1f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
using Content.Client.Resources;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.ResourceManagement;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
|
|
||||||
namespace Content.Client.DoAfter.UI;
|
|
||||||
|
|
||||||
public sealed class DoAfterControl : PanelContainer
|
|
||||||
{
|
|
||||||
public float Ratio
|
|
||||||
{
|
|
||||||
get => _bar.Ratio;
|
|
||||||
set => _bar.Ratio = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Cancelled
|
|
||||||
{
|
|
||||||
get => _bar.Cancelled;
|
|
||||||
set => _bar.Cancelled = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DoAfterBar _bar;
|
|
||||||
|
|
||||||
public DoAfterControl()
|
|
||||||
{
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
|
|
||||||
var cache = IoCManager.Resolve<IResourceCache>();
|
|
||||||
|
|
||||||
AddChild(new TextureRect
|
|
||||||
{
|
|
||||||
Texture = cache.GetTexture("/Textures/Interface/Misc/progress_bar.rsi/icon.png"),
|
|
||||||
TextureScale = Vector2.One * DoAfterBar.DoAfterBarScale,
|
|
||||||
VerticalAlignment = VAlignment.Center,
|
|
||||||
});
|
|
||||||
|
|
||||||
_bar = new DoAfterBar();
|
|
||||||
AddChild(_bar);
|
|
||||||
VerticalAlignment = VAlignment.Bottom;
|
|
||||||
_bar.Measure(Vector2.Infinity);
|
|
||||||
Measure(Vector2.Infinity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
using Content.Shared.DoAfter;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Shared.Timing;
|
|
||||||
|
|
||||||
namespace Content.Client.DoAfter.UI
|
|
||||||
{
|
|
||||||
public sealed class DoAfterGui : BoxContainer
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
|
||||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
|
||||||
|
|
||||||
private readonly Dictionary<byte, DoAfterControl> _doAfterControls = new();
|
|
||||||
|
|
||||||
// We'll store cancellations for a little bit just so we can flash the graphic to indicate it's cancelled
|
|
||||||
private readonly Dictionary<byte, TimeSpan> _cancelledDoAfters = new();
|
|
||||||
|
|
||||||
public EntityUid? AttachedEntity { get; set; }
|
|
||||||
|
|
||||||
public DoAfterGui()
|
|
||||||
{
|
|
||||||
Orientation = LayoutOrientation.Vertical;
|
|
||||||
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(this);
|
|
||||||
SeparationOverride = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
if (Disposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (_, control) in _doAfterControls)
|
|
||||||
{
|
|
||||||
control.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_doAfterControls.Clear();
|
|
||||||
_cancelledDoAfters.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add the necessary control for a DoAfter progress bar.
|
|
||||||
/// </summary>
|
|
||||||
public void AddDoAfter(ClientDoAfter message)
|
|
||||||
{
|
|
||||||
if (_doAfterControls.ContainsKey(message.ID))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var doAfterBar = new DoAfterControl();
|
|
||||||
AddChild(doAfterBar);
|
|
||||||
_doAfterControls.Add(message.ID, doAfterBar);
|
|
||||||
Measure(Vector2.Infinity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE THAT THE BELOW ONLY HANDLES THE UI SIDE
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a DoAfter without showing a cancel graphic.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id"></param>
|
|
||||||
public void RemoveDoAfter(byte id)
|
|
||||||
{
|
|
||||||
if (!_doAfterControls.ContainsKey(id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var control = _doAfterControls[id];
|
|
||||||
RemoveChild(control);
|
|
||||||
control.DisposeAllChildren();
|
|
||||||
_doAfterControls.Remove(id);
|
|
||||||
_cancelledDoAfters.Remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cancels a DoAfter and shows a graphic indicating it has been cancelled to the player.
|
|
||||||
/// </summary>
|
|
||||||
/// Can be called multiple times on the 1 DoAfter because of the client predicting the cancellation.
|
|
||||||
/// <param name="id"></param>
|
|
||||||
public void CancelDoAfter(byte id)
|
|
||||||
{
|
|
||||||
if (_cancelledDoAfters.ContainsKey(id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_doAfterControls.TryGetValue(id, out var doAfterControl))
|
|
||||||
{
|
|
||||||
doAfterControl = new DoAfterControl();
|
|
||||||
AddChild(doAfterControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
doAfterControl.Cancelled = true;
|
|
||||||
_cancelledDoAfters.Add(id, _gameTiming.CurTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
|
||||||
{
|
|
||||||
base.FrameUpdate(args);
|
|
||||||
|
|
||||||
if (!_entityManager.TryGetComponent(AttachedEntity, out DoAfterComponent? doAfterComponent))
|
|
||||||
{
|
|
||||||
Visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var doAfters = doAfterComponent.DoAfters;
|
|
||||||
|
|
||||||
if (doAfters.Count == 0)
|
|
||||||
{
|
|
||||||
Visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var transform = _entityManager.GetComponent<TransformComponent>(AttachedEntity.Value);
|
|
||||||
|
|
||||||
if (_eyeManager.CurrentMap != transform.MapID || !transform.Coordinates.IsValid(_entityManager))
|
|
||||||
{
|
|
||||||
Visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Visible = true;
|
|
||||||
var currentTime = _gameTiming.CurTime;
|
|
||||||
var toRemove = new List<byte>();
|
|
||||||
|
|
||||||
// Cleanup cancelled DoAfters
|
|
||||||
foreach (var (id, cancelTime) in _cancelledDoAfters)
|
|
||||||
{
|
|
||||||
if ((currentTime - cancelTime).TotalSeconds > DoAfterSystem.ExcessTime)
|
|
||||||
toRemove.Add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var id in toRemove)
|
|
||||||
{
|
|
||||||
RemoveDoAfter(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
toRemove.Clear();
|
|
||||||
|
|
||||||
// Update 0 -> 1.0f of the things
|
|
||||||
foreach (var (id, message) in doAfters)
|
|
||||||
{
|
|
||||||
if (_cancelledDoAfters.ContainsKey(id) || !_doAfterControls.ContainsKey(id))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var control = _doAfterControls[id];
|
|
||||||
var ratio = (currentTime - message.StartTime).TotalSeconds;
|
|
||||||
control.Ratio = MathF.Min(1.0f,
|
|
||||||
(float) ratio / message.Delay);
|
|
||||||
|
|
||||||
// Just in case it doesn't get cleaned up by the system for whatever reason.
|
|
||||||
if (ratio > message.Delay + DoAfterSystem.ExcessTime)
|
|
||||||
{
|
|
||||||
toRemove.Add(id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var id in toRemove)
|
|
||||||
{
|
|
||||||
RemoveDoAfter(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdatePosition(transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdatePosition(TransformComponent xform)
|
|
||||||
{
|
|
||||||
var screenCoordinates = _eyeManager.CoordinatesToScreen(xform.Coordinates);
|
|
||||||
var position = screenCoordinates.Position / UIScale - DesiredSize / 2f;
|
|
||||||
LayoutContainer.SetPosition(this, position - new Vector2(0, 40f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,12 +34,20 @@ namespace Content.Shared.DoAfter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Merge this with the actual DoAfter
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// We send a trimmed-down version of the DoAfter for the client for it to use.
|
/// We send a trimmed-down version of the DoAfter for the client for it to use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class ClientDoAfter
|
public sealed class ClientDoAfter
|
||||||
{
|
{
|
||||||
|
public bool Cancelled = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Accrued time when cancelled.
|
||||||
|
/// </summary>
|
||||||
|
public float CancelledAccumulator;
|
||||||
|
|
||||||
// To see what these do look at DoAfter and DoAfterEventArgs
|
// To see what these do look at DoAfter and DoAfterEventArgs
|
||||||
public byte ID { get; }
|
public byte ID { get; }
|
||||||
|
|
||||||
@@ -51,6 +59,8 @@ namespace Content.Shared.DoAfter
|
|||||||
|
|
||||||
public EntityUid? Target { get; }
|
public EntityUid? Target { get; }
|
||||||
|
|
||||||
|
public float Accumulator;
|
||||||
|
|
||||||
public float Delay { get; }
|
public float Delay { get; }
|
||||||
|
|
||||||
// TODO: The other ones need predicting
|
// TODO: The other ones need predicting
|
||||||
|
|||||||
Reference in New Issue
Block a user