179 lines
7.7 KiB
C#
179 lines
7.7 KiB
C#
using System.Numerics;
|
|
using System.Threading;
|
|
using Content.Shared._DV.CosmicCult;
|
|
using Content.Shared._DV.CosmicCult.Components;
|
|
using Content.Shared.Actions;
|
|
using Content.Shared.Actions.Components;
|
|
using Content.Shared.Actions.Events;
|
|
using Content.Shared.Maps;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.Player;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Timing;
|
|
|
|
using Timer = Robust.Shared.Timing.Timer;
|
|
|
|
namespace Content.Client._DV.CosmicCult.Visuals;
|
|
|
|
/// <summary>
|
|
/// This handles rendering a preview of where the monument will be placed
|
|
/// </summary>
|
|
public sealed class MonumentPlacementPreviewSystem : EntitySystem
|
|
{
|
|
//most of these aren't used by this system, see MonumentPlacementPreviewOverlay for a note on why they're here
|
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
[Dependency] private readonly IOverlayManager _overlay = default!;
|
|
[Dependency] private readonly IPlayerManager _player = default!;
|
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
|
[Dependency] private readonly TurfSystem _turf = default!;
|
|
[Dependency] private readonly SharedMapSystem _map = default!;
|
|
[Dependency] private readonly TransformSystem _transform = default!;
|
|
|
|
private MonumentPlacementPreviewOverlay? _cachedOverlay;
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
|
|
private const int MinimumDistanceFromSpace = 3;
|
|
|
|
/// <inheritdoc/>
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<MonumentPlacementPreviewComponent, ActionAttemptEvent>(OnAttemptMonumentPlacement);
|
|
SubscribeLocalEvent<CosmicCultLeadComponent, EventCosmicPlaceMonument>(OnCosmicPlaceMonument);
|
|
SubscribeLocalEvent<CosmicCultLeadComponent, EventCosmicMoveMonument>(OnCosmicMoveMonument);
|
|
}
|
|
|
|
private void DoMonumentAnimation(EntityUid performer)
|
|
{
|
|
if (_cachedOverlay == null || _cancellationTokenSource == null)
|
|
return;
|
|
|
|
if (!VerifyPlacement(Transform(performer), out _))
|
|
return;
|
|
|
|
_cachedOverlay.LockPlacement = true;
|
|
_cancellationTokenSource.Cancel(); //cancel the previous timer
|
|
|
|
//remove the overlay automatically after the primeTime expires
|
|
//no cancellation token for this one as this'll never need to get cancelled afaik
|
|
Timer.Spawn(TimeSpan.FromSeconds(3.8), //anim takes 3.8s, might want to have the ghost disappear earlier but eh
|
|
() =>
|
|
{
|
|
_overlay.RemoveOverlay<MonumentPlacementPreviewOverlay>();
|
|
_cachedOverlay = null;
|
|
_cancellationTokenSource = null;
|
|
}
|
|
);
|
|
}
|
|
|
|
private void OnCosmicMoveMonument(Entity<CosmicCultLeadComponent> ent, ref EventCosmicMoveMonument args)
|
|
{
|
|
DoMonumentAnimation(args.Performer);
|
|
}
|
|
|
|
private void OnCosmicPlaceMonument(Entity<CosmicCultLeadComponent> ent, ref EventCosmicPlaceMonument args)
|
|
{
|
|
DoMonumentAnimation(args.Performer);
|
|
}
|
|
|
|
//duplicated from the ability check, minus the station check because that can't be done clientside afaik?
|
|
//and no popups because they're done in the ability check as well
|
|
public bool VerifyPlacement(TransformComponent xform, out EntityCoordinates outPos)
|
|
{
|
|
outPos = new EntityCoordinates();
|
|
|
|
//MAKE SURE WE'RE STANDING ON A GRID
|
|
if (!TryComp(xform.GridUid, out MapGridComponent? grid))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//CHECK IF IT'S BEING PLACED CHEESILY CLOSE TO SPACE
|
|
var worldPos = _transform.GetWorldPosition(xform); //this is technically wrong but basically fine; if
|
|
foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(worldPos, MinimumDistanceFromSpace)))
|
|
{
|
|
if(_turf.IsSpace(tile))
|
|
return false;
|
|
}
|
|
|
|
var localTile = _map.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
|
|
var targetIndices = localTile.GridIndices + new Vector2i(0, 1);
|
|
var pos = _map.ToCenterCoordinates(xform.GridUid.Value, targetIndices, grid);
|
|
outPos = pos;
|
|
var box = new Box2(pos.Position + new Vector2(-1.4f, -0.4f), pos.Position + new Vector2(1.4f, 0.4f));
|
|
|
|
//CHECK FOR ENTITY AND ENVIRONMENTAL INTERSECTIONS
|
|
if (_lookup.AnyLocalEntitiesIntersecting(xform.GridUid.Value, box, LookupFlags.Dynamic | LookupFlags.Static, _player.LocalEntity))
|
|
return false;
|
|
|
|
//if all of those aren't false, return true
|
|
return true;
|
|
}
|
|
|
|
private void OnAttemptMonumentPlacement(Entity<MonumentPlacementPreviewComponent> ent, ref ActionAttemptEvent args)
|
|
{
|
|
if (!TryComp<ConfirmableActionComponent>(ent, out var confirmableAction))
|
|
return; //return if the action somehow doesn't have a confirmableAction comp
|
|
|
|
//if we've already got a cached overlay, reset the timers & bump alpha back up to 1.
|
|
//todo do that
|
|
//should probably smoothly transition alpha back up to 1 but idrc (this will bother me a lot I'm lying) it's an incredibly specific thing that occurs in a .25s window at the end of a 10s wait
|
|
//not a great solution but I'm not sure if a Real:tm: (also not entirely sure what a Real:tm: fix would be here tbh? hooking into ActionAttemptEvent?) fix would actually work here? need to investigate.
|
|
if (_cachedOverlay != null && _cancellationTokenSource != null)
|
|
{
|
|
_cancellationTokenSource.Cancel();
|
|
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
StartTimers(confirmableAction, _cancellationTokenSource, _cachedOverlay);
|
|
|
|
if (_cachedOverlay.FadingOut) //if we're fading out
|
|
{
|
|
_cachedOverlay.FadingOut = false; //stop it
|
|
|
|
var progress = (1 - (_cachedOverlay.FadeOutProgress / _cachedOverlay.FadeOutTime)) * _cachedOverlay.FadeInTime; //set fade in progress to 1 - fade out progress (so 70% out becomes 30% in)
|
|
_cachedOverlay.FadeInProgress = progress;
|
|
_cachedOverlay.FadingIn = true; //start fading in again
|
|
_cachedOverlay.FadeOutProgress = 0; //stop the fadeout entirely
|
|
} //no need for a special fade in case as well, that can go as normal
|
|
|
|
return;
|
|
}
|
|
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
//it's probably inefficient to make a new one every time, but this'll be happening like four times a round maybe
|
|
//massive ctor because iocmanager hates me
|
|
_cachedOverlay = new MonumentPlacementPreviewOverlay(EntityManager, _player, _proto, _timing, ent.Comp.Tier);
|
|
_overlay.AddOverlay(_cachedOverlay);
|
|
|
|
StartTimers(confirmableAction, _cancellationTokenSource, _cachedOverlay);
|
|
}
|
|
|
|
private void StartTimers(ConfirmableActionComponent comp, CancellationTokenSource tokenSource, MonumentPlacementPreviewOverlay overlay)
|
|
{
|
|
//remove the overlay automatically after the primeTime expires
|
|
Timer.Spawn(comp.PrimeTime + comp.ConfirmDelay,
|
|
() =>
|
|
{
|
|
_overlay.RemoveOverlay<MonumentPlacementPreviewOverlay>();
|
|
_cachedOverlay = null;
|
|
_cancellationTokenSource = null;
|
|
},
|
|
tokenSource.Token
|
|
);
|
|
|
|
//start a timer to start the fade out as well, with the same cancellation token
|
|
Timer.Spawn(comp.PrimeTime + comp.ConfirmDelay - TimeSpan.FromSeconds(overlay.FadeOutTime),
|
|
() =>
|
|
{
|
|
overlay.FadingOut = true;
|
|
},
|
|
tokenSource.Token
|
|
);
|
|
}
|
|
}
|