Re-organize all projects (#4166)
This commit is contained in:
@@ -1,204 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Shared.AI;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.AI
|
||||
{
|
||||
#if DEBUG
|
||||
public class ClientAiDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private AiDebugMode _tooltips = AiDebugMode.None;
|
||||
private readonly Dictionary<IEntity, PanelContainer> _aiBoxes = new();
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
if (_tooltips == 0)
|
||||
{
|
||||
if (_aiBoxes.Count > 0)
|
||||
{
|
||||
foreach (var (_, panel) in _aiBoxes)
|
||||
{
|
||||
panel.Dispose();
|
||||
}
|
||||
|
||||
_aiBoxes.Clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var deletedEntities = new List<IEntity>(0);
|
||||
foreach (var (entity, panel) in _aiBoxes)
|
||||
{
|
||||
if (entity.Deleted)
|
||||
{
|
||||
deletedEntities.Add(entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_eyeManager.GetWorldViewport().Contains(entity.Transform.WorldPosition))
|
||||
{
|
||||
panel.Visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var (x, y) = _eyeManager.CoordinatesToScreen(entity.Transform.Coordinates).Position;
|
||||
var offsetPosition = new Vector2(x - panel.Width / 2, y - panel.Height - 50f);
|
||||
panel.Visible = true;
|
||||
|
||||
LayoutContainer.SetPosition(panel, offsetPosition);
|
||||
}
|
||||
|
||||
foreach (var entity in deletedEntities)
|
||||
{
|
||||
_aiBoxes.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<SharedAiDebug.UtilityAiDebugMessage>(HandleUtilityAiDebugMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.AStarRouteMessage>(HandleAStarRouteMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.JpsRouteMessage>(HandleJpsRouteMessage);
|
||||
}
|
||||
|
||||
private void HandleUtilityAiDebugMessage(SharedAiDebug.UtilityAiDebugMessage message)
|
||||
{
|
||||
if ((_tooltips & AiDebugMode.Thonk) != 0)
|
||||
{
|
||||
// I guess if it's out of range we don't know about it?
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var entity = entityManager.GetEntity(message.EntityUid);
|
||||
TryCreatePanel(entity);
|
||||
|
||||
// Probably shouldn't access by index but it's a debugging tool so eh
|
||||
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(0);
|
||||
label.Text = $"Current Task: {message.FoundTask}\n" +
|
||||
$"Task score: {message.ActionScore}\n" +
|
||||
$"Planning time (ms): {message.PlanningTime * 1000:0.0000}\n" +
|
||||
$"Considered {message.ConsideredTaskCount} tasks";
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||
{
|
||||
if ((_tooltips & AiDebugMode.Paths) != 0)
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var entity = entityManager.GetEntity(message.EntityUid);
|
||||
TryCreatePanel(entity);
|
||||
|
||||
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(1);
|
||||
label.Text = $"Pathfinding time (ms): {message.TimeTaken * 1000:0.0000}\n" +
|
||||
$"Nodes traversed: {message.CameFrom.Count}\n" +
|
||||
$"Nodes per ms: {message.CameFrom.Count / (message.TimeTaken * 1000)}";
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||
{
|
||||
if ((_tooltips & AiDebugMode.Paths) != 0)
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var entity = entityManager.GetEntity(message.EntityUid);
|
||||
TryCreatePanel(entity);
|
||||
|
||||
var label = (Label) _aiBoxes[entity].GetChild(0).GetChild(1);
|
||||
label.Text = $"Pathfinding time (ms): {message.TimeTaken * 1000:0.0000}\n" +
|
||||
$"Jump Nodes: {message.JumpNodes.Count}\n" +
|
||||
$"Jump Nodes per ms: {message.JumpNodes.Count / (message.TimeTaken * 1000)}";
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
foreach (var tooltip in _aiBoxes.Values)
|
||||
{
|
||||
tooltip.Dispose();
|
||||
}
|
||||
_aiBoxes.Clear();
|
||||
_tooltips = AiDebugMode.None;
|
||||
}
|
||||
|
||||
|
||||
private void EnableTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
_tooltips |= tooltip;
|
||||
}
|
||||
|
||||
private void DisableTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
_tooltips &= ~tooltip;
|
||||
}
|
||||
|
||||
public void ToggleTooltip(AiDebugMode tooltip)
|
||||
{
|
||||
if ((_tooltips & tooltip) != 0)
|
||||
{
|
||||
DisableTooltip(tooltip);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryCreatePanel(IEntity entity)
|
||||
{
|
||||
if (!_aiBoxes.ContainsKey(entity))
|
||||
{
|
||||
var userInterfaceManager = IoCManager.Resolve<IUserInterfaceManager>();
|
||||
|
||||
var actionLabel = new Label
|
||||
{
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
};
|
||||
|
||||
var pathfindingLabel = new Label
|
||||
{
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
};
|
||||
|
||||
var vBox = new VBoxContainer()
|
||||
{
|
||||
SeparationOverride = 15,
|
||||
Children = {actionLabel, pathfindingLabel},
|
||||
};
|
||||
|
||||
var panel = new PanelContainer
|
||||
{
|
||||
StyleClasses = { StyleNano.StyleClassTooltipPanel },
|
||||
Children = {vBox},
|
||||
MouseFilter = Control.MouseFilterMode.Ignore,
|
||||
ModulateSelfOverride = Color.White.WithAlpha(0.75f),
|
||||
};
|
||||
|
||||
userInterfaceManager.StateRoot.AddChild(panel);
|
||||
|
||||
_aiBoxes[entity] = panel;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AiDebugMode : byte
|
||||
{
|
||||
None = 0,
|
||||
Paths = 1 << 1,
|
||||
Thonk = 1 << 2,
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,518 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.AI;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.AI
|
||||
{
|
||||
#if DEBUG
|
||||
public class ClientPathfindingDebugSystem : EntitySystem
|
||||
{
|
||||
private PathfindingDebugMode _modes = PathfindingDebugMode.None;
|
||||
private float _routeDuration = 4.0f; // How long before we remove a route from the overlay
|
||||
private DebugPathfindingOverlay? _overlay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<SharedAiDebug.AStarRouteMessage>(HandleAStarRouteMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.JpsRouteMessage>(HandleJpsRouteMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.PathfindingGraphMessage>(HandleGraphMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.ReachableChunkRegionsDebugMessage>(HandleRegionsMessage);
|
||||
SubscribeNetworkEvent<SharedAiDebug.ReachableCacheDebugMessage>(HandleCachedRegionsMessage);
|
||||
// I'm lazy
|
||||
EnableOverlay();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
DisableOverlay();
|
||||
}
|
||||
|
||||
private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message)
|
||||
{
|
||||
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(_modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
_overlay?.AStarRoutes.Add(message);
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
_overlay.AStarRoutes.Remove(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message)
|
||||
{
|
||||
if ((_modes & PathfindingDebugMode.Nodes) != 0 ||
|
||||
(_modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
_overlay?.JpsRoutes.Add(message);
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () =>
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
_overlay.JpsRoutes.Remove(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleGraphMessage(SharedAiDebug.PathfindingGraphMessage message)
|
||||
{
|
||||
EnableOverlay().UpdateGraph(message.Graph);
|
||||
}
|
||||
|
||||
private void HandleRegionsMessage(SharedAiDebug.ReachableChunkRegionsDebugMessage message)
|
||||
{
|
||||
EnableOverlay().UpdateRegions(message.GridId, message.Regions);
|
||||
}
|
||||
|
||||
private void HandleCachedRegionsMessage(SharedAiDebug.ReachableCacheDebugMessage message)
|
||||
{
|
||||
EnableOverlay().UpdateCachedRegions(message.GridId, message.Regions, message.Cached);
|
||||
}
|
||||
|
||||
private DebugPathfindingOverlay EnableOverlay()
|
||||
{
|
||||
if (_overlay != null)
|
||||
{
|
||||
return _overlay;
|
||||
}
|
||||
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_overlay = new DebugPathfindingOverlay {Modes = _modes};
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
|
||||
return _overlay;
|
||||
}
|
||||
|
||||
private void DisableOverlay()
|
||||
{
|
||||
if (_overlay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_overlay.Modes = 0;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
overlayManager.RemoveOverlay(_overlay);
|
||||
_overlay = null;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
_modes = PathfindingDebugMode.None;
|
||||
DisableOverlay();
|
||||
}
|
||||
|
||||
|
||||
private void EnableMode(PathfindingDebugMode tooltip)
|
||||
{
|
||||
_modes |= tooltip;
|
||||
if (_modes != 0)
|
||||
{
|
||||
EnableOverlay();
|
||||
}
|
||||
|
||||
if (_overlay != null)
|
||||
{
|
||||
_overlay.Modes = _modes;
|
||||
}
|
||||
|
||||
if (tooltip == PathfindingDebugMode.Graph)
|
||||
{
|
||||
RaiseNetworkEvent(new SharedAiDebug.RequestPathfindingGraphMessage());
|
||||
}
|
||||
|
||||
if (tooltip == PathfindingDebugMode.Regions)
|
||||
{
|
||||
RaiseNetworkEvent(new SharedAiDebug.SubscribeReachableMessage());
|
||||
}
|
||||
|
||||
// TODO: Request region graph, although the client system messages didn't seem to be going through anymore
|
||||
// So need further investigation.
|
||||
}
|
||||
|
||||
private void DisableMode(PathfindingDebugMode mode)
|
||||
{
|
||||
if (mode == PathfindingDebugMode.Regions && (_modes & PathfindingDebugMode.Regions) != 0)
|
||||
{
|
||||
RaiseNetworkEvent(new SharedAiDebug.UnsubscribeReachableMessage());
|
||||
}
|
||||
|
||||
_modes &= ~mode;
|
||||
if (_modes == 0)
|
||||
{
|
||||
DisableOverlay();
|
||||
}
|
||||
else if (_overlay != null)
|
||||
{
|
||||
_overlay.Modes = _modes;
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleTooltip(PathfindingDebugMode mode)
|
||||
{
|
||||
if ((_modes & mode) != 0)
|
||||
{
|
||||
DisableMode(mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DebugPathfindingOverlay : Overlay
|
||||
{
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IPlayerManager _playerManager;
|
||||
|
||||
// TODO: Add a box like the debug one and show the most recent path stuff
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public PathfindingDebugMode Modes { get; set; } = PathfindingDebugMode.None;
|
||||
|
||||
// Graph debugging
|
||||
public readonly Dictionary<int, List<Vector2>> Graph = new();
|
||||
private readonly Dictionary<int, Color> _graphColors = new();
|
||||
|
||||
// Cached regions
|
||||
public readonly Dictionary<GridId, Dictionary<int, List<Vector2>>> CachedRegions =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<GridId, Dictionary<int, Color>> _cachedRegionColors =
|
||||
new();
|
||||
|
||||
// Regions
|
||||
public readonly Dictionary<GridId, Dictionary<int, Dictionary<int, List<Vector2>>>> Regions =
|
||||
new();
|
||||
|
||||
private readonly Dictionary<GridId, Dictionary<int, Dictionary<int, Color>>> _regionColors =
|
||||
new();
|
||||
|
||||
// Route debugging
|
||||
// As each pathfinder is very different you'll likely want to draw them completely different
|
||||
public readonly List<SharedAiDebug.AStarRouteMessage> AStarRoutes = new();
|
||||
public readonly List<SharedAiDebug.JpsRouteMessage> JpsRoutes = new();
|
||||
|
||||
public DebugPathfindingOverlay()
|
||||
{
|
||||
_shader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("unshaded").Instance();
|
||||
_eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||
_playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
}
|
||||
|
||||
#region Graph
|
||||
public void UpdateGraph(Dictionary<int, List<Vector2>> graph)
|
||||
{
|
||||
Graph.Clear();
|
||||
_graphColors.Clear();
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
foreach (var (chunk, nodes) in graph)
|
||||
{
|
||||
Graph[chunk] = nodes;
|
||||
_graphColors[chunk] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(),
|
||||
robustRandom.NextFloat(), 0.3f);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGraph(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var (chunk, nodes) in Graph)
|
||||
{
|
||||
foreach (var tile in nodes)
|
||||
{
|
||||
if (!viewport.Contains(tile)) continue;
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, _graphColors[chunk]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Regions
|
||||
//Server side debugger should increment every region
|
||||
public void UpdateCachedRegions(GridId gridId, Dictionary<int, List<Vector2>> messageRegions, bool cached)
|
||||
{
|
||||
if (!CachedRegions.ContainsKey(gridId))
|
||||
{
|
||||
CachedRegions.Add(gridId, new Dictionary<int, List<Vector2>>());
|
||||
_cachedRegionColors.Add(gridId, new Dictionary<int, Color>());
|
||||
}
|
||||
|
||||
foreach (var (region, nodes) in messageRegions)
|
||||
{
|
||||
CachedRegions[gridId][region] = nodes;
|
||||
if (cached)
|
||||
{
|
||||
_cachedRegionColors[gridId][region] = Color.Blue.WithAlpha(0.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedRegionColors[gridId][region] = Color.Green.WithAlpha(0.3f);
|
||||
}
|
||||
|
||||
Timer.Spawn(3000, () =>
|
||||
{
|
||||
if (CachedRegions[gridId].ContainsKey(region))
|
||||
{
|
||||
CachedRegions[gridId].Remove(region);
|
||||
_cachedRegionColors[gridId].Remove(region);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCachedRegions(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
var attachedEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (attachedEntity == null || !CachedRegions.TryGetValue(attachedEntity.Transform.GridID, out var entityRegions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (region, nodes) in entityRegions)
|
||||
{
|
||||
foreach (var tile in nodes)
|
||||
{
|
||||
if (!viewport.Contains(tile)) continue;
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, _cachedRegionColors[attachedEntity.Transform.GridID][region]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateRegions(GridId gridId, Dictionary<int, Dictionary<int, List<Vector2>>> messageRegions)
|
||||
{
|
||||
if (!Regions.ContainsKey(gridId))
|
||||
{
|
||||
Regions.Add(gridId, new Dictionary<int, Dictionary<int, List<Vector2>>>());
|
||||
_regionColors.Add(gridId, new Dictionary<int, Dictionary<int, Color>>());
|
||||
}
|
||||
|
||||
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
foreach (var (chunk, regions) in messageRegions)
|
||||
{
|
||||
Regions[gridId][chunk] = new Dictionary<int, List<Vector2>>();
|
||||
_regionColors[gridId][chunk] = new Dictionary<int, Color>();
|
||||
|
||||
foreach (var (region, nodes) in regions)
|
||||
{
|
||||
Regions[gridId][chunk].Add(region, nodes);
|
||||
_regionColors[gridId][chunk][region] = new Color(robustRandom.NextFloat(), robustRandom.NextFloat(),
|
||||
robustRandom.NextFloat(), 0.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRegions(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
var attachedEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (attachedEntity == null || !Regions.TryGetValue(attachedEntity.Transform.GridID, out var entityRegions))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (chunk, regions) in entityRegions)
|
||||
{
|
||||
foreach (var (region, nodes) in regions)
|
||||
{
|
||||
foreach (var tile in nodes)
|
||||
{
|
||||
if (!viewport.Contains(tile)) continue;
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, _regionColors[attachedEntity.Transform.GridID][chunk][region]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Pathfinder
|
||||
private void DrawAStarRoutes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in AStarRoutes)
|
||||
{
|
||||
// Draw box on each tile of route
|
||||
foreach (var position in route.Route)
|
||||
{
|
||||
if (!viewport.Contains(position)) continue;
|
||||
var screenTile = _eyeManager.WorldToScreen(position);
|
||||
// worldHandle.DrawLine(position, nextWorld.Value, Color.Blue);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
screenHandle.DrawRect(box, Color.Orange.WithAlpha(0.25f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAStarNodes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in AStarRoutes)
|
||||
{
|
||||
var highestGScore = route.GScores.Values.Max();
|
||||
|
||||
foreach (var (tile, score) in route.GScores)
|
||||
{
|
||||
if ((route.Route.Contains(tile) && (Modes & PathfindingDebugMode.Route) != 0) ||
|
||||
!viewport.Contains(tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, new Color(
|
||||
0.0f,
|
||||
score / highestGScore,
|
||||
1.0f - (score / highestGScore),
|
||||
0.1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJpsRoutes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in JpsRoutes)
|
||||
{
|
||||
// Draw box on each tile of route
|
||||
foreach (var position in route.Route)
|
||||
{
|
||||
if (!viewport.Contains(position)) continue;
|
||||
var screenTile = _eyeManager.WorldToScreen(position);
|
||||
// worldHandle.DrawLine(position, nextWorld.Value, Color.Blue);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
screenHandle.DrawRect(box, Color.Orange.WithAlpha(0.25f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJpsNodes(DrawingHandleScreen screenHandle, Box2 viewport)
|
||||
{
|
||||
foreach (var route in JpsRoutes)
|
||||
{
|
||||
foreach (var tile in route.JumpNodes)
|
||||
{
|
||||
if ((route.Route.Contains(tile) && (Modes & PathfindingDebugMode.Route) != 0) ||
|
||||
!viewport.Contains(tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var screenTile = _eyeManager.WorldToScreen(tile);
|
||||
var box = new UIBox2(
|
||||
screenTile.X - 15.0f,
|
||||
screenTile.Y - 15.0f,
|
||||
screenTile.X + 15.0f,
|
||||
screenTile.Y + 15.0f);
|
||||
|
||||
screenHandle.DrawRect(box, new Color(
|
||||
0.0f,
|
||||
1.0f,
|
||||
0.0f,
|
||||
0.2f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Modes == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var screenHandle = args.ScreenHandle;
|
||||
screenHandle.UseShader(_shader);
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Route) != 0)
|
||||
{
|
||||
DrawAStarRoutes(screenHandle, viewport);
|
||||
DrawJpsRoutes(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Nodes) != 0)
|
||||
{
|
||||
DrawAStarNodes(screenHandle, viewport);
|
||||
DrawJpsNodes(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Graph) != 0)
|
||||
{
|
||||
DrawGraph(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.CachedRegions) != 0)
|
||||
{
|
||||
DrawCachedRegions(screenHandle, viewport);
|
||||
}
|
||||
|
||||
if ((Modes & PathfindingDebugMode.Regions) != 0)
|
||||
{
|
||||
DrawRegions(screenHandle, viewport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PathfindingDebugMode : byte
|
||||
{
|
||||
None = 0,
|
||||
Route = 1 << 0,
|
||||
Graph = 1 << 1,
|
||||
Nodes = 1 << 2,
|
||||
CachedRegions = 1 << 3,
|
||||
Regions = 1 << 4,
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ActionsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// set up hotkeys for hotbar
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenActionsMenu,
|
||||
InputCmdHandler.FromDelegate(_ => ToggleActionsMenu()))
|
||||
.Bind(ContentKeyFunctions.Hotbar1,
|
||||
HandleHotbarKeybind(0))
|
||||
.Bind(ContentKeyFunctions.Hotbar2,
|
||||
HandleHotbarKeybind(1))
|
||||
.Bind(ContentKeyFunctions.Hotbar3,
|
||||
HandleHotbarKeybind(2))
|
||||
.Bind(ContentKeyFunctions.Hotbar4,
|
||||
HandleHotbarKeybind(3))
|
||||
.Bind(ContentKeyFunctions.Hotbar5,
|
||||
HandleHotbarKeybind(4))
|
||||
.Bind(ContentKeyFunctions.Hotbar6,
|
||||
HandleHotbarKeybind(5))
|
||||
.Bind(ContentKeyFunctions.Hotbar7,
|
||||
HandleHotbarKeybind(6))
|
||||
.Bind(ContentKeyFunctions.Hotbar8,
|
||||
HandleHotbarKeybind(7))
|
||||
.Bind(ContentKeyFunctions.Hotbar9,
|
||||
HandleHotbarKeybind(8))
|
||||
.Bind(ContentKeyFunctions.Hotbar0,
|
||||
HandleHotbarKeybind(9))
|
||||
.Bind(ContentKeyFunctions.Loadout1,
|
||||
HandleChangeHotbarKeybind(0))
|
||||
.Bind(ContentKeyFunctions.Loadout2,
|
||||
HandleChangeHotbarKeybind(1))
|
||||
.Bind(ContentKeyFunctions.Loadout3,
|
||||
HandleChangeHotbarKeybind(2))
|
||||
.Bind(ContentKeyFunctions.Loadout4,
|
||||
HandleChangeHotbarKeybind(3))
|
||||
.Bind(ContentKeyFunctions.Loadout5,
|
||||
HandleChangeHotbarKeybind(4))
|
||||
.Bind(ContentKeyFunctions.Loadout6,
|
||||
HandleChangeHotbarKeybind(5))
|
||||
.Bind(ContentKeyFunctions.Loadout7,
|
||||
HandleChangeHotbarKeybind(6))
|
||||
.Bind(ContentKeyFunctions.Loadout8,
|
||||
HandleChangeHotbarKeybind(7))
|
||||
.Bind(ContentKeyFunctions.Loadout9,
|
||||
HandleChangeHotbarKeybind(8))
|
||||
// when selecting a target, we intercept clicks in the game world, treating them as our target selection. We want to
|
||||
// take priority before any other systems handle the click.
|
||||
.BindBefore(EngineKeyFunctions.Use, new PointerInputCmdHandler(TargetingOnUse),
|
||||
typeof(ConstructionSystem), typeof(DragDropSystem))
|
||||
.Register<ActionsSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
CommandBinds.Unregister<ActionsSystem>();
|
||||
}
|
||||
|
||||
private PointerInputCmdHandler HandleHotbarKeybind(byte slot)
|
||||
{
|
||||
// delegate to the ActionsUI, simulating a click on it
|
||||
return new((in PointerInputCmdHandler.PointerInputCmdArgs args) =>
|
||||
{
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (playerEntity == null ||
|
||||
!playerEntity.TryGetComponent<ClientActionsComponent>(out var actionsComponent)) return false;
|
||||
|
||||
actionsComponent.HandleHotbarKeybind(slot, args);
|
||||
return true;
|
||||
}, false);
|
||||
}
|
||||
|
||||
private PointerInputCmdHandler HandleChangeHotbarKeybind(byte hotbar)
|
||||
{
|
||||
// delegate to the ActionsUI, simulating a click on it
|
||||
return new((in PointerInputCmdHandler.PointerInputCmdArgs args) =>
|
||||
{
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (playerEntity == null ||
|
||||
!playerEntity.TryGetComponent<ClientActionsComponent>( out var actionsComponent)) return false;
|
||||
|
||||
actionsComponent.HandleChangeHotbarKeybind(hotbar, args);
|
||||
return true;
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
private bool TargetingOnUse(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (playerEntity == null ||
|
||||
!playerEntity.TryGetComponent<ClientActionsComponent>( out var actionsComponent)) return false;
|
||||
|
||||
return actionsComponent.TargetingOnUse(args);
|
||||
}
|
||||
|
||||
private void ToggleActionsMenu()
|
||||
{
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (playerEntity == null ||
|
||||
!playerEntity.TryGetComponent<ClientActionsComponent>( out var actionsComponent)) return;
|
||||
|
||||
actionsComponent.ToggleActionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Client.Interfaces;
|
||||
using Content.Shared.Audio;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Client;
|
||||
using Robust.Client.State;
|
||||
using Content.Client.State;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class BackgroundAudioSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IClientGameTicker _clientGameTicker = default!;
|
||||
|
||||
private SoundCollectionPrototype _ambientCollection = default!;
|
||||
|
||||
private AudioParams _ambientParams = new(-10f, 1, "Master", 0, 0, true, 0f);
|
||||
private AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, true, 0f);
|
||||
|
||||
private IPlayingAudioStream? _ambientStream;
|
||||
private IPlayingAudioStream? _lobbyStream;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_ambientCollection = _prototypeManager.Index<SoundCollectionPrototype>("AmbienceBase");
|
||||
|
||||
_configManager.OnValueChanged(CCVars.AmbienceBasicEnabled, AmbienceCVarChanged);
|
||||
_configManager.OnValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||
|
||||
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerJoinedServer += OnJoin;
|
||||
_client.PlayerLeaveServer += OnLeave;
|
||||
|
||||
_clientGameTicker.LobbyStatusUpdated += LobbySongReceived;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerJoinedServer -= OnJoin;
|
||||
_client.PlayerLeaveServer -= OnLeave;
|
||||
|
||||
_clientGameTicker.LobbyStatusUpdated -= LobbySongReceived;
|
||||
|
||||
EndAmbience();
|
||||
EndLobbyMusic();
|
||||
}
|
||||
|
||||
private void StateManagerOnStateChanged(StateChangedEventArgs args)
|
||||
{
|
||||
EndAmbience();
|
||||
EndLobbyMusic();
|
||||
if (args.NewState is LobbyState && _configManager.GetCVar(CCVars.LobbyMusicEnabled))
|
||||
{
|
||||
StartLobbyMusic();
|
||||
}
|
||||
else if (args.NewState is GameScreen && _configManager.GetCVar(CCVars.AmbienceBasicEnabled))
|
||||
{
|
||||
StartAmbience();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJoin(object? sender, PlayerEventArgs args)
|
||||
{
|
||||
if (_stateManager.CurrentState is LobbyState)
|
||||
{
|
||||
EndAmbience();
|
||||
if (_configManager.GetCVar(CCVars.LobbyMusicEnabled))
|
||||
{
|
||||
StartLobbyMusic();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EndLobbyMusic();
|
||||
if (_configManager.GetCVar(CCVars.AmbienceBasicEnabled))
|
||||
{
|
||||
StartAmbience();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLeave(object? sender, PlayerEventArgs args)
|
||||
{
|
||||
EndAmbience();
|
||||
EndLobbyMusic();
|
||||
}
|
||||
|
||||
private void AmbienceCVarChanged(bool ambienceEnabled)
|
||||
{
|
||||
if (!ambienceEnabled)
|
||||
{
|
||||
EndAmbience();
|
||||
}
|
||||
else if (_stateManager.CurrentState is GameScreen)
|
||||
{
|
||||
StartAmbience();
|
||||
}
|
||||
else
|
||||
{
|
||||
EndAmbience();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartAmbience()
|
||||
{
|
||||
EndAmbience();
|
||||
var file = _robustRandom.Pick(_ambientCollection.PickFiles);
|
||||
_ambientStream = SoundSystem.Play(Filter.Local(), file, _ambientParams);
|
||||
}
|
||||
|
||||
private void EndAmbience()
|
||||
{
|
||||
_ambientStream?.Stop();
|
||||
_ambientStream = null;
|
||||
}
|
||||
|
||||
private void LobbyMusicCVarChanged(bool musicEnabled)
|
||||
{
|
||||
if (!musicEnabled)
|
||||
{
|
||||
EndLobbyMusic();
|
||||
}
|
||||
else if (_stateManager.CurrentState is LobbyState)
|
||||
{
|
||||
StartLobbyMusic();
|
||||
}
|
||||
else
|
||||
{
|
||||
EndLobbyMusic();
|
||||
}
|
||||
}
|
||||
|
||||
private void LobbySongReceived()
|
||||
{
|
||||
if (_lobbyStream != null) //Toggling Ready status fires this method. This check ensures we only start the lobby music if it's not playing.
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_stateManager.CurrentState is LobbyState && _configManager.GetCVar(CCVars.LobbyMusicEnabled))
|
||||
{
|
||||
StartLobbyMusic();
|
||||
}
|
||||
}
|
||||
private void StartLobbyMusic()
|
||||
{
|
||||
EndLobbyMusic();
|
||||
var file = _clientGameTicker.LobbySong;
|
||||
if (file == null) // We have not received the lobby song yet.
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lobbyStream = SoundSystem.Play(Filter.Local(), file, _lobbyParams);
|
||||
}
|
||||
|
||||
private void EndLobbyMusic()
|
||||
{
|
||||
_lobbyStream?.Stop();
|
||||
_lobbyStream = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class CameraRecoilSystem : EntitySystem
|
||||
{
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var recoil in EntityManager.ComponentManager.EntityQuery<CameraRecoilComponent>(true))
|
||||
{
|
||||
recoil.FrameUpdate(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Actor;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class CharacterInterfaceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameHud _gameHud = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenCharacterMenu,
|
||||
InputCmdHandler.FromDelegate(s => HandleOpenCharacterMenu()))
|
||||
.Register<CharacterInterfaceSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<CharacterInterfaceSystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void HandleOpenCharacterMenu()
|
||||
{
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity == null
|
||||
|| !_playerManager.LocalPlayer.ControlledEntity.TryGetComponent(out CharacterInterface? characterInterface))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var menu = characterInterface.Window;
|
||||
|
||||
if (menu == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (menu.IsOpen)
|
||||
{
|
||||
if (menu.IsAtFront())
|
||||
{
|
||||
_setOpenValue(menu, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.MoveToFront();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_setOpenValue(menu, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void _setOpenValue(SS14Window menu, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_gameHud.CharacterButtonDown = true;
|
||||
menu.OpenCentered();
|
||||
}
|
||||
else
|
||||
{
|
||||
_gameHud.CharacterButtonDown = false;
|
||||
menu.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.NewFolder
|
||||
{
|
||||
public class ChemicalReactionSystem : SharedChemicalReactionSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.HUD.Inventory;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class ClientInventorySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameHud _gameHud = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenInventoryMenu,
|
||||
InputCmdHandler.FromDelegate(_ => HandleOpenInventoryMenu()))
|
||||
.Register<ClientInventorySystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<ClientInventorySystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void HandleOpenInventoryMenu()
|
||||
{
|
||||
_gameHud.InventoryButtonDown = !_gameHud.InventoryButtonDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class CombatModeSystem : SharedCombatModeSystem
|
||||
{
|
||||
[Dependency] private readonly IGameHud _gameHud = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_gameHud.OnTargetingZoneChanged = OnTargetingZoneChanged;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<CombatModeSystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
public bool IsInCombatMode()
|
||||
{
|
||||
var entity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (entity == null || !entity.TryGetComponent(out CombatModeComponent? combatMode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return combatMode.IsInCombatMode;
|
||||
}
|
||||
|
||||
private void OnTargetingZoneChanged(TargetingZone obj)
|
||||
{
|
||||
EntityManager.RaisePredictiveEvent(new CombatModeSystemMessages.SetTargetZoneMessage(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.GameObjects.Components.Construction;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Utility;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// The client-side implementation of the construction system, which is used for constructing entities in game.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ConstructionSystem : SharedConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
private readonly Dictionary<int, ConstructionGhostComponent> _ghosts = new();
|
||||
|
||||
private int _nextId;
|
||||
|
||||
private bool CraftingEnabled { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PlayerAttachSysMessage>(HandlePlayerAttached);
|
||||
SubscribeNetworkEvent<AckStructureConstructionMessage>(HandleAckStructure);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenCraftingMenu,
|
||||
new PointerInputCmdHandler(HandleOpenCraftingMenu))
|
||||
.Bind(EngineKeyFunctions.Use,
|
||||
new PointerInputCmdHandler(HandleUse))
|
||||
.Register<ConstructionSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
UnsubscribeLocalEvent<PlayerAttachSysMessage>();
|
||||
UnsubscribeNetworkEvent<AckStructureConstructionMessage>();
|
||||
|
||||
CommandBinds.Unregister<ConstructionSystem>();
|
||||
}
|
||||
|
||||
public event EventHandler<CraftingAvailabilityChangedArgs>? CraftingAvailabilityChanged;
|
||||
public event EventHandler? ToggleCraftingWindow;
|
||||
|
||||
private void HandleAckStructure(AckStructureConstructionMessage msg)
|
||||
{
|
||||
ClearGhost(msg.GhostId);
|
||||
}
|
||||
|
||||
private void HandlePlayerAttached(PlayerAttachSysMessage msg)
|
||||
{
|
||||
var available = IsCrafingAvailable(msg.AttachedEntity);
|
||||
UpdateCraftingAvailability(available);
|
||||
}
|
||||
|
||||
private bool HandleOpenCraftingMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (args.State == BoundKeyState.Down)
|
||||
ToggleCraftingWindow?.Invoke(this, EventArgs.Empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateCraftingAvailability(bool available)
|
||||
{
|
||||
if (CraftingEnabled == available)
|
||||
return;
|
||||
|
||||
CraftingAvailabilityChanged?.Invoke(this, new CraftingAvailabilityChangedArgs(available));
|
||||
CraftingEnabled = available;
|
||||
}
|
||||
|
||||
private static bool IsCrafingAvailable(IEntity? entity)
|
||||
{
|
||||
if (entity == null)
|
||||
return false;
|
||||
|
||||
// TODO: Decide if entity can craft, using capabilities or something
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleUse(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (!args.EntityUid.IsValid() || !args.EntityUid.IsClientSide())
|
||||
return false;
|
||||
|
||||
var entity = EntityManager.GetEntity(args.EntityUid);
|
||||
|
||||
if (!entity.TryGetComponent<ConstructionGhostComponent>(out var ghostComp))
|
||||
return false;
|
||||
|
||||
TryStartConstruction(ghostComp.GhostId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a construction ghost at the given location.
|
||||
/// </summary>
|
||||
public void SpawnGhost(ConstructionPrototype prototype, EntityCoordinates loc, Direction dir)
|
||||
{
|
||||
var user = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
|
||||
if (user == null || GhostPresent(loc) || !user.InRangeUnobstructed(loc, 20f, ignoreInsideBlocker: prototype.CanBuildInImpassable)) return;
|
||||
|
||||
foreach (var condition in prototype.Conditions)
|
||||
{
|
||||
if (!condition.Condition(user, loc, dir))
|
||||
return;
|
||||
}
|
||||
|
||||
var ghost = EntityManager.SpawnEntity("constructionghost", loc);
|
||||
var comp = ghost.GetComponent<ConstructionGhostComponent>();
|
||||
comp.Prototype = prototype;
|
||||
comp.GhostId = _nextId++;
|
||||
ghost.Transform.LocalRotation = dir.ToAngle();
|
||||
_ghosts.Add(comp.GhostId, comp);
|
||||
var sprite = ghost.GetComponent<SpriteComponent>();
|
||||
sprite.Color = new Color(48, 255, 48, 128);
|
||||
sprite.AddBlankLayer(0); // There is no way to actually check if this already exists, so we blindly insert a new one
|
||||
sprite.LayerSetSprite(0, prototype.Icon);
|
||||
sprite.LayerSetShader(0, "unshaded");
|
||||
sprite.LayerSetVisible(0, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any construction ghosts are present at the given position
|
||||
/// </summary>
|
||||
private bool GhostPresent(EntityCoordinates loc)
|
||||
{
|
||||
foreach (var ghost in _ghosts)
|
||||
{
|
||||
if (ghost.Value.Owner.Transform.Coordinates.Equals(loc)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TryStartConstruction(int ghostId)
|
||||
{
|
||||
var ghost = _ghosts[ghostId];
|
||||
|
||||
if (ghost.Prototype == null)
|
||||
{
|
||||
throw new ArgumentException($"Can't start construction for a ghost with no prototype. Ghost id: {ghostId}");
|
||||
}
|
||||
|
||||
var transform = ghost.Owner.Transform;
|
||||
var msg = new TryStartStructureConstructionMessage(transform.Coordinates, ghost.Prototype.ID, transform.LocalRotation, ghostId);
|
||||
RaiseNetworkEvent(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts constructing an item underneath the attached entity.
|
||||
/// </summary>
|
||||
public void TryStartItemConstruction(string prototypeName)
|
||||
{
|
||||
RaiseNetworkEvent(new TryStartItemConstructionMessage(prototypeName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a construction ghost entity with the given ID.
|
||||
/// </summary>
|
||||
public void ClearGhost(int ghostId)
|
||||
{
|
||||
if (_ghosts.TryGetValue(ghostId, out var ghost))
|
||||
{
|
||||
ghost.Owner.QueueDelete();
|
||||
_ghosts.Remove(ghostId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all construction ghosts.
|
||||
/// </summary>
|
||||
public void ClearAllGhosts()
|
||||
{
|
||||
foreach (var (_, ghost) in _ghosts)
|
||||
{
|
||||
ghost.Owner.QueueDelete();
|
||||
}
|
||||
|
||||
_ghosts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class CraftingAvailabilityChangedArgs : EventArgs
|
||||
{
|
||||
public bool Available { get; }
|
||||
|
||||
public CraftingAvailabilityChangedArgs(bool available)
|
||||
{
|
||||
Available = available;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Client.State;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.UserInterface.ContextMenu;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public class ContextMenuPresenter : IDisposable
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
||||
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private readonly IContextMenuView _contextMenuView;
|
||||
private readonly VerbSystem _verbSystem;
|
||||
|
||||
private bool _playerCanSeeThroughContainers;
|
||||
|
||||
private MapCoordinates _mapCoordinates;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
public ContextMenuPresenter(VerbSystem verbSystem)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_verbSystem = verbSystem;
|
||||
_verbSystem.ToggleContextMenu += SystemOnToggleContextMenu;
|
||||
_verbSystem.ToggleContainerVisibility += SystemOnToggleContainerVisibility;
|
||||
|
||||
_contextMenuView = new ContextMenuView();
|
||||
_contextMenuView.OnKeyBindDownSingle += OnKeyBindDownSingle;
|
||||
_contextMenuView.OnMouseEnteredSingle += OnMouseEnteredSingle;
|
||||
_contextMenuView.OnMouseExitedSingle += OnMouseExitedSingle;
|
||||
_contextMenuView.OnMouseHoveringSingle += OnMouseHoveringSingle;
|
||||
|
||||
_contextMenuView.OnKeyBindDownStack += OnKeyBindDownStack;
|
||||
_contextMenuView.OnMouseEnteredStack += OnMouseEnteredStack;
|
||||
|
||||
_contextMenuView.OnExitedTree += OnExitedTree;
|
||||
_contextMenuView.OnCloseRootMenu += OnCloseRootMenu;
|
||||
_contextMenuView.OnCloseChildMenu += OnCloseChildMenu;
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ContextMenuGroupingType, _contextMenuView.OnGroupingContextMenuChanged, true);
|
||||
}
|
||||
|
||||
#region View Events
|
||||
private void OnCloseChildMenu(object? sender, int depth)
|
||||
{
|
||||
_contextMenuView.CloseContextPopups(depth);
|
||||
}
|
||||
|
||||
private void OnCloseRootMenu(object? sender, EventArgs e)
|
||||
{
|
||||
_contextMenuView.CloseContextPopups();
|
||||
}
|
||||
|
||||
private void OnExitedTree(object? sender, ContextMenuElement e)
|
||||
{
|
||||
_contextMenuView.UpdateParents(e);
|
||||
}
|
||||
|
||||
private void OnMouseEnteredStack(object? sender, StackContextElement e)
|
||||
{
|
||||
var realGlobalPosition = e.GlobalPosition;
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new();
|
||||
|
||||
Timer.Spawn(e.HoverDelay, () =>
|
||||
{
|
||||
_verbSystem.CloseGroupMenu();
|
||||
|
||||
if (_contextMenuView.Menus.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0);
|
||||
|
||||
var filteredEntities = e.ContextEntities.Where(entity => !entity.Deleted);
|
||||
if (filteredEntities.Any())
|
||||
{
|
||||
_contextMenuView.AddChildMenu(filteredEntities, realGlobalPosition, e);
|
||||
}
|
||||
}, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private void OnKeyBindDownStack(object? sender, (GUIBoundKeyEventArgs, StackContextElement) e)
|
||||
{
|
||||
var (args, stack) = e;
|
||||
var firstEntity = stack.ContextEntities.FirstOrDefault(ent => !ent.Deleted);
|
||||
|
||||
if (firstEntity == null) return;
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject)
|
||||
{
|
||||
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var func = args.Function;
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(func);
|
||||
|
||||
var message = new FullInputCmdMessage(_gameTiming.CurTick, _gameTiming.TickFraction, funcId,
|
||||
BoundKeyState.Down, firstEntity.Transform.Coordinates, args.PointerLocation, firstEntity.Uid);
|
||||
|
||||
var session = _playerManager.LocalPlayer?.Session;
|
||||
if (session != null)
|
||||
{
|
||||
inputSys.HandleInputCommand(session, func, message);
|
||||
}
|
||||
CloseAllMenus();
|
||||
args.Handle();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_itemSlotManager.OnButtonPressed(args, firstEntity))
|
||||
{
|
||||
CloseAllMenus();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseHoveringSingle(object? sender, SingleContextElement e)
|
||||
{
|
||||
if (!e.DrawOutline) return;
|
||||
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if (localPlayer?.ControlledEntity != null)
|
||||
{
|
||||
var inRange =
|
||||
localPlayer.InRangeUnobstructed(e.ContextEntity, ignoreInsideBlocker: true);
|
||||
|
||||
// BUG: This assumes that the main viewport is the viewport that the context menu is active on.
|
||||
// This is not necessarily true but we currently have no way to find the viewport (reliably)
|
||||
// from the input event.
|
||||
//
|
||||
// This might be particularly important in the future with a more advanced mapping mode.
|
||||
var renderScale = _eyeManager.MainViewport.GetRenderScale();
|
||||
e.OutlineComponent?.UpdateInRange(inRange, renderScale);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseEnteredSingle(object? sender, SingleContextElement e)
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
var entity = e.ContextEntity;
|
||||
_verbSystem.CloseGroupMenu();
|
||||
|
||||
OnCloseChildMenu(sender, e.ParentMenu?.Depth ?? 0);
|
||||
|
||||
if (entity.Deleted) return;
|
||||
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if (localPlayer?.ControlledEntity == null) return;
|
||||
|
||||
var renderScale = _eyeManager.MainViewport.GetRenderScale();
|
||||
e.OutlineComponent?.OnMouseEnter(localPlayer.InRangeUnobstructed(entity, ignoreInsideBlocker: true), renderScale);
|
||||
if (e.SpriteComp != null)
|
||||
{
|
||||
e.SpriteComp.DrawDepth = (int) Shared.GameObjects.DrawDepth.HighlightedItems;
|
||||
}
|
||||
e.DrawOutline = true;
|
||||
}
|
||||
|
||||
private void OnMouseExitedSingle(object? sender, SingleContextElement e)
|
||||
{
|
||||
if (!e.ContextEntity.Deleted)
|
||||
{
|
||||
if (e.SpriteComp != null)
|
||||
{
|
||||
e.SpriteComp.DrawDepth = e.OriginalDrawDepth;
|
||||
}
|
||||
e.OutlineComponent?.OnMouseLeave();
|
||||
}
|
||||
e.DrawOutline = false;
|
||||
}
|
||||
|
||||
private void OnKeyBindDownSingle(object? sender, (GUIBoundKeyEventArgs, SingleContextElement) valueTuple)
|
||||
{
|
||||
var (args, single) = valueTuple;
|
||||
var entity = single.ContextEntity;
|
||||
if (args.Function == ContentKeyFunctions.OpenContextMenu)
|
||||
{
|
||||
_verbSystem.OnContextButtonPressed(entity);
|
||||
args.Handle();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function == ContentKeyFunctions.ExamineEntity)
|
||||
{
|
||||
_systemManager.GetEntitySystem<ExamineSystem>().DoExamine(entity);
|
||||
args.Handle();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function == EngineKeyFunctions.Use || args.Function == ContentKeyFunctions.Point ||
|
||||
args.Function == ContentKeyFunctions.TryPullObject || args.Function == ContentKeyFunctions.MovePulledObject)
|
||||
{
|
||||
var inputSys = _systemManager.GetEntitySystem<InputSystem>();
|
||||
|
||||
var func = args.Function;
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(func);
|
||||
|
||||
var message = new FullInputCmdMessage(_gameTiming.CurTick, _gameTiming.TickFraction, funcId,
|
||||
BoundKeyState.Down, entity.Transform.Coordinates, args.PointerLocation, entity.Uid);
|
||||
|
||||
var session = _playerManager.LocalPlayer?.Session;
|
||||
if (session != null)
|
||||
{
|
||||
inputSys.HandleInputCommand(session, func, message);
|
||||
}
|
||||
|
||||
CloseAllMenus();
|
||||
args.Handle();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_itemSlotManager.OnButtonPressed(args, single.ContextEntity))
|
||||
{
|
||||
CloseAllMenus();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Model Updates
|
||||
private void SystemOnToggleContainerVisibility(object? sender, bool args)
|
||||
{
|
||||
_playerCanSeeThroughContainers = args;
|
||||
}
|
||||
|
||||
private void SystemOnToggleContextMenu(object? sender, PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (_stateManager.CurrentState is not GameScreenBase)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (playerEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_mapCoordinates = args.Coordinates.ToMap(_entityManager);
|
||||
if (!_verbSystem.TryGetContextEntities(playerEntity, _mapCoordinates, out var entities))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
entities = entities.Where(CanSeeOnContextMenu).ToList();
|
||||
if (entities.Count > 0)
|
||||
{
|
||||
_contextMenuView.AddRootMenu(entities);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleMoveEvent(MoveEvent ev)
|
||||
{
|
||||
if (_contextMenuView.Elements.Count == 0) return;
|
||||
var entity = ev.Sender;
|
||||
if (_contextMenuView.Elements.ContainsKey(entity))
|
||||
{
|
||||
if (!entity.Transform.MapPosition.InRange(_mapCoordinates, 1.0f))
|
||||
{
|
||||
_contextMenuView.RemoveEntity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_contextMenuView.Elements.Count == 0) return;
|
||||
|
||||
foreach (var entity in _contextMenuView.Elements.Keys.ToList())
|
||||
{
|
||||
if (entity.Deleted || !_playerCanSeeThroughContainers && entity.IsInContainer())
|
||||
{
|
||||
_contextMenuView.RemoveEntity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private bool CanSeeOnContextMenu(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out ISpriteComponent? spriteComponent) || !spriteComponent.Visible)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entity.GetAllComponents<IShowContextMenu>().Any(s => !s.ShowContextMenu(entity)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _playerCanSeeThroughContainers || !entity.TryGetContainer(out var container) || container.ShowContents;
|
||||
}
|
||||
|
||||
private void CloseAllMenus()
|
||||
{
|
||||
_contextMenuView.CloseContextPopups();
|
||||
_verbSystem.CloseGroupMenu();
|
||||
_verbSystem.CloseVerbMenu();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_verbSystem.ToggleContextMenu -= SystemOnToggleContextMenu;
|
||||
_verbSystem.ToggleContainerVisibility -= SystemOnToggleContainerVisibility;
|
||||
|
||||
_contextMenuView.OnKeyBindDownSingle -= OnKeyBindDownSingle;
|
||||
_contextMenuView.OnMouseEnteredSingle -= OnMouseEnteredSingle;
|
||||
_contextMenuView.OnMouseExitedSingle -= OnMouseExitedSingle;
|
||||
_contextMenuView.OnMouseHoveringSingle -= OnMouseHoveringSingle;
|
||||
|
||||
_contextMenuView.OnKeyBindDownStack -= OnKeyBindDownStack;
|
||||
_contextMenuView.OnMouseEnteredStack -= OnMouseEnteredStack;
|
||||
|
||||
_contextMenuView.OnExitedTree -= OnExitedTree;
|
||||
_contextMenuView.OnCloseRootMenu -= OnCloseRootMenu;
|
||||
_contextMenuView.OnCloseChildMenu -= OnCloseChildMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +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.GameObjects.EntitySystems.DoAfter
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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,217 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.GameObjects.Components;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
||||
{
|
||||
public sealed class DoAfterGui : VBoxContainer
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private readonly Dictionary<byte, PanelContainer> _doAfterControls = new();
|
||||
private readonly Dictionary<byte, DoAfterBar> _doAfterBars = 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 IEntity? AttachedEntity { get; set; }
|
||||
private ScreenCoordinates _playerPosition;
|
||||
|
||||
public DoAfterGui()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(this);
|
||||
SeparationOverride = 0;
|
||||
|
||||
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (Disposed)
|
||||
return;
|
||||
|
||||
foreach (var (_, control) in _doAfterControls)
|
||||
{
|
||||
control.Dispose();
|
||||
}
|
||||
|
||||
_doAfterControls.Clear();
|
||||
_doAfterBars.Clear();
|
||||
_cancelledDoAfters.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the necessary control for a DoAfter progress bar.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void AddDoAfter(ClientDoAfter message)
|
||||
{
|
||||
if (_doAfterControls.ContainsKey(message.ID))
|
||||
return;
|
||||
|
||||
var doAfterBar = new DoAfterBar
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center
|
||||
};
|
||||
|
||||
_doAfterBars[message.ID] = doAfterBar;
|
||||
|
||||
var control = new PanelContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/Misc/progress_bar.rsi/icon.png"),
|
||||
TextureScale = Vector2.One * DoAfterBar.DoAfterBarScale,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
},
|
||||
|
||||
doAfterBar
|
||||
}
|
||||
};
|
||||
|
||||
AddChild(control);
|
||||
_doAfterControls.Add(message.ID, control);
|
||||
}
|
||||
|
||||
// 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);
|
||||
_doAfterBars.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;
|
||||
|
||||
DoAfterBar doAfterBar;
|
||||
|
||||
if (!_doAfterControls.TryGetValue(id, out var doAfterControl))
|
||||
{
|
||||
doAfterControl = new PanelContainer();
|
||||
AddChild(doAfterControl);
|
||||
DebugTools.Assert(!_doAfterBars.ContainsKey(id));
|
||||
doAfterBar = new DoAfterBar();
|
||||
doAfterControl.AddChild(doAfterBar);
|
||||
_doAfterBars[id] = doAfterBar;
|
||||
}
|
||||
else
|
||||
{
|
||||
doAfterBar = _doAfterBars[id];
|
||||
}
|
||||
|
||||
doAfterBar.Cancelled = true;
|
||||
_cancelledDoAfters.Add(id, _gameTiming.CurTime);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (AttachedEntity?.IsValid() != true ||
|
||||
!AttachedEntity.TryGetComponent(out DoAfterComponent? doAfterComponent))
|
||||
{
|
||||
Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var doAfters = doAfterComponent.DoAfters;
|
||||
if (doAfters.Count == 0)
|
||||
{
|
||||
Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_eyeManager.CurrentMap != AttachedEntity.Transform.MapID ||
|
||||
!AttachedEntity.Transform.Coordinates.IsValid(_entityManager))
|
||||
{
|
||||
Visible = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
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 doAfterBar = _doAfterBars[id];
|
||||
var ratio = (currentTime - message.StartTime).TotalSeconds;
|
||||
doAfterBar.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);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in toRemove)
|
||||
{
|
||||
RemoveDoAfter(id);
|
||||
}
|
||||
|
||||
var screenCoordinates = _eyeManager.CoordinatesToScreen(AttachedEntity.Transform.Coordinates);
|
||||
_playerPosition = new ScreenCoordinates(screenCoordinates.Position / UIScale, screenCoordinates.Window);
|
||||
LayoutContainer.SetPosition(this, new Vector2(_playerPosition.X - Width / 2, _playerPosition.Y - Height - 30.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles events that need to happen after a certain amount of time where the event could be cancelled by factors
|
||||
/// such as moving.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
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!;
|
||||
|
||||
/// <summary>
|
||||
/// We'll use an excess time so stuff like finishing effects can show.
|
||||
/// </summary>
|
||||
public const float ExcessTime = 0.5f;
|
||||
|
||||
private IEntity? _attachedEntity;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PlayerAttachSysMessage>(HandlePlayerAttached);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
UnsubscribeLocalEvent<PlayerAttachSysMessage>();
|
||||
}
|
||||
|
||||
private void HandlePlayerAttached(PlayerAttachSysMessage message)
|
||||
{
|
||||
_attachedEntity = message.AttachedEntity;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var currentTime = _gameTiming.CurTime;
|
||||
|
||||
// Can't see any I guess?
|
||||
if (_attachedEntity == null || _attachedEntity.Deleted)
|
||||
return;
|
||||
|
||||
var viewbox = _eyeManager.GetWorldViewport().Enlarged(2.0f);
|
||||
|
||||
foreach (var comp in ComponentManager.EntityQuery<DoAfterComponent>(true))
|
||||
{
|
||||
var doAfters = comp.DoAfters.ToList();
|
||||
var compPos = comp.Owner.Transform.WorldPosition;
|
||||
|
||||
if (doAfters.Count == 0 ||
|
||||
comp.Owner.Transform.MapID != _attachedEntity.Transform.MapID ||
|
||||
!viewbox.Contains(compPos))
|
||||
{
|
||||
comp.Disable();
|
||||
continue;
|
||||
}
|
||||
|
||||
var range = (compPos - _attachedEntity.Transform.WorldPosition).Length +
|
||||
0.01f;
|
||||
|
||||
if (comp.Owner != _attachedEntity &&
|
||||
!ExamineSystemShared.InRangeUnOccluded(
|
||||
_attachedEntity.Transform.MapPosition,
|
||||
comp.Owner.Transform.MapPosition, range,
|
||||
entity => entity == comp.Owner || entity == _attachedEntity))
|
||||
{
|
||||
comp.Disable();
|
||||
continue;
|
||||
}
|
||||
|
||||
comp.Enable();
|
||||
|
||||
var userGrid = comp.Owner.Transform.Coordinates;
|
||||
|
||||
// Check cancellations / finishes
|
||||
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 (elapsedTime > doAfter.Delay + ExcessTime)
|
||||
{
|
||||
comp.Remove(doAfter);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't predict cancellation if it's already finished.
|
||||
if (elapsedTime > doAfter.Delay)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Predictions
|
||||
if (doAfter.BreakOnUserMove)
|
||||
{
|
||||
if (!userGrid.InRange(EntityManager, doAfter.UserGrid, doAfter.MovementThreshold))
|
||||
{
|
||||
comp.Cancel(id, currentTime);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (doAfter.BreakOnTargetMove)
|
||||
{
|
||||
if (EntityManager.TryGetEntity(doAfter.TargetUid, out var targetEntity) &&
|
||||
!targetEntity.Transform.Coordinates.InRange(EntityManager, doAfter.TargetGrid,
|
||||
doAfter.MovementThreshold))
|
||||
{
|
||||
comp.Cancel(id, currentTime);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var count = comp.CancelledDoAfters.Count;
|
||||
// Remove cancelled DoAfters after ExcessTime has elapsed
|
||||
for (var i = count - 1; i >= 0; i--)
|
||||
{
|
||||
var cancelled = comp.CancelledDoAfters[i];
|
||||
if ((currentTime - cancelled.CancelTime).TotalSeconds > ExcessTime)
|
||||
{
|
||||
comp.Remove(cancelled.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.GameObjects.Components.Doors;
|
||||
using Content.Shared.GameObjects.Components.Doors;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by the client to "predict" when doors will change how collideable they are as part of their opening / closing.
|
||||
/// </summary>
|
||||
internal sealed class DoorSystem : SharedDoorSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// List of doors that need to be periodically checked.
|
||||
/// </summary>
|
||||
private readonly List<ClientDoorComponent> _activeDoors = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DoorStateMessage>(HandleDoorState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers doors to be periodically checked.
|
||||
/// </summary>
|
||||
/// <param name="message">A message corresponding to the component under consideration, raised when its state changes.</param>
|
||||
private void HandleDoorState(DoorStateMessage message)
|
||||
{
|
||||
switch (message.State)
|
||||
{
|
||||
case SharedDoorComponent.DoorState.Closed:
|
||||
case SharedDoorComponent.DoorState.Open:
|
||||
_activeDoors.Remove(message.Component);
|
||||
break;
|
||||
case SharedDoorComponent.DoorState.Closing:
|
||||
case SharedDoorComponent.DoorState.Opening:
|
||||
_activeDoors.Add(message.Component);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
for (var i = _activeDoors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var comp = _activeDoors[i];
|
||||
if (comp.Deleted)
|
||||
{
|
||||
_activeDoors.RemoveAt(i);
|
||||
}
|
||||
comp.OnUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,455 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.State;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using DrawDepth = Content.Shared.GameObjects.DrawDepth;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles clientside drag and drop logic
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class DragDropSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
// how often to recheck possible targets (prevents calling expensive
|
||||
// check logic each update)
|
||||
private const float TargetRecheckInterval = 0.25f;
|
||||
|
||||
// if a drag ends up being cancelled and it has been under this
|
||||
// amount of time since the mousedown, we will "replay" the original
|
||||
// mousedown event so it can be treated like a regular click
|
||||
private const float MaxMouseDownTimeForReplayingClick = 0.85f;
|
||||
|
||||
private const string ShaderDropTargetInRange = "SelectionOutlineInrange";
|
||||
private const string ShaderDropTargetOutOfRange = "SelectionOutline";
|
||||
|
||||
// entity performing the drag action
|
||||
|
||||
private IEntity? _dragger;
|
||||
private readonly List<IDraggable> _draggables = new();
|
||||
private IEntity? _dragShadow;
|
||||
|
||||
// time since mouse down over the dragged entity
|
||||
private float _mouseDownTime;
|
||||
// how much time since last recheck of all possible targets
|
||||
private float _targetRecheckTime;
|
||||
// reserved initial mousedown event so we can replay it if no drag ends up being performed
|
||||
private PointerInputCmdHandler.PointerInputCmdArgs? _savedMouseDown;
|
||||
// whether we are currently replaying the original mouse down, so we
|
||||
// can ignore any events sent to this system
|
||||
private bool _isReplaying;
|
||||
|
||||
private DragDropHelper<IEntity> _dragDropHelper = default!;
|
||||
|
||||
private ShaderInstance? _dropTargetInRangeShader;
|
||||
private ShaderInstance? _dropTargetOutOfRangeShader;
|
||||
private SharedInteractionSystem _interactionSystem = default!;
|
||||
private InputSystem _inputSystem = default!;
|
||||
|
||||
private readonly List<ISpriteComponent> _highlightedSprites = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_dragDropHelper = new DragDropHelper<IEntity>(OnBeginDrag, OnContinueDrag, OnEndDrag);
|
||||
|
||||
_dropTargetInRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetInRange).Instance();
|
||||
_dropTargetOutOfRangeShader = _prototypeManager.Index<ShaderPrototype>(ShaderDropTargetOutOfRange).Instance();
|
||||
_interactionSystem = Get<SharedInteractionSystem>();
|
||||
_inputSystem = Get<InputSystem>();
|
||||
// needs to fire on mouseup and mousedown so we can detect a drag / drop
|
||||
CommandBinds.Builder
|
||||
.Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(OnUse, false))
|
||||
.Register<DragDropSystem>();
|
||||
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_dragDropHelper.EndDrag();
|
||||
CommandBinds.Unregister<DragDropSystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private bool OnUse(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
// not currently predicted
|
||||
if (_inputSystem.Predicted) return false;
|
||||
|
||||
// currently replaying a saved click, don't handle this because
|
||||
// we already decided this click doesn't represent an actual drag attempt
|
||||
if (_isReplaying) return false;
|
||||
|
||||
if (args.State == BoundKeyState.Down)
|
||||
{
|
||||
return OnUseMouseDown(args);
|
||||
}
|
||||
else if (args.State == BoundKeyState.Up)
|
||||
{
|
||||
return OnUseMouseUp(args);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OnUseMouseDown(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (args.Session?.AttachedEntity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var dragger = args.Session.AttachedEntity;
|
||||
// cancel any current dragging if there is one (shouldn't be because they would've had to have lifted
|
||||
// the mouse, canceling the drag, but just being cautious)
|
||||
_dragDropHelper.EndDrag();
|
||||
|
||||
// possibly initiating a drag
|
||||
// check if the clicked entity is draggable
|
||||
if (!EntityManager.TryGetEntity(args.EntityUid, out var entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the entity is reachable
|
||||
if (!_interactionSystem.InRangeUnobstructed(dragger, entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var canDrag = false;
|
||||
foreach (var draggable in entity.GetAllComponents<IDraggable>())
|
||||
{
|
||||
var dragEventArgs = new StartDragDropEvent(dragger, entity);
|
||||
|
||||
if (!draggable.CanStartDrag(dragEventArgs))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_draggables.Add(draggable);
|
||||
canDrag = true;
|
||||
}
|
||||
|
||||
if (!canDrag)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait to initiate a drag
|
||||
_dragDropHelper.MouseDown(entity);
|
||||
_dragger = dragger;
|
||||
_mouseDownTime = 0;
|
||||
|
||||
// don't want anything else to process the click,
|
||||
// but we will save the event so we can "re-play" it if this drag does
|
||||
// not turn into an actual drag so the click can be handled normally
|
||||
_savedMouseDown = args;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private bool OnBeginDrag()
|
||||
{
|
||||
if (_dragDropHelper.Dragged == null || _dragDropHelper.Dragged.Deleted)
|
||||
{
|
||||
// something happened to the clicked entity or we moved the mouse off the target so
|
||||
// we shouldn't replay the original click
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_dragDropHelper.Dragged.TryGetComponent<SpriteComponent>(out var draggedSprite))
|
||||
{
|
||||
// pop up drag shadow under mouse
|
||||
var mousePos = _eyeManager.ScreenToMap(_dragDropHelper.MouseScreenPosition);
|
||||
_dragShadow = EntityManager.SpawnEntity("dragshadow", mousePos);
|
||||
var dragSprite = _dragShadow.GetComponent<SpriteComponent>();
|
||||
dragSprite.CopyFrom(draggedSprite);
|
||||
dragSprite.RenderOrder = EntityManager.CurrentTick.Value;
|
||||
dragSprite.Color = dragSprite.Color.WithAlpha(0.7f);
|
||||
// keep it on top of everything
|
||||
dragSprite.DrawDepth = (int) DrawDepth.Overlays;
|
||||
if (dragSprite.Directional)
|
||||
{
|
||||
_dragShadow.Transform.WorldRotation = _dragDropHelper.Dragged.Transform.WorldRotation;
|
||||
}
|
||||
|
||||
HighlightTargets();
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, new OutlineToggleMessage(false));
|
||||
|
||||
// drag initiated
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.Warning("Unable to display drag shadow for {0} because it" +
|
||||
" has no sprite component.", _dragDropHelper.Dragged.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OnContinueDrag(float frameTime)
|
||||
{
|
||||
if (_dragDropHelper.Dragged == null || _dragDropHelper.Dragged.Deleted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_dragger);
|
||||
|
||||
// still in range of the thing we are dragging?
|
||||
if (!_interactionSystem.InRangeUnobstructed(_dragger!, _dragDropHelper.Dragged))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// keep dragged entity under mouse
|
||||
var mousePos = _eyeManager.ScreenToMap(_dragDropHelper.MouseScreenPosition);
|
||||
// TODO: would use MapPosition instead if it had a setter, but it has no setter.
|
||||
// is that intentional, or should we add a setter for Transform.MapPosition?
|
||||
if (_dragShadow == null)
|
||||
return false;
|
||||
|
||||
_dragShadow.Transform.WorldPosition = mousePos.Position;
|
||||
|
||||
_targetRecheckTime += frameTime;
|
||||
if (_targetRecheckTime > TargetRecheckInterval)
|
||||
{
|
||||
HighlightTargets();
|
||||
_targetRecheckTime = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnEndDrag()
|
||||
{
|
||||
RemoveHighlights();
|
||||
if (_dragShadow != null)
|
||||
{
|
||||
EntityManager.DeleteEntity(_dragShadow);
|
||||
}
|
||||
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Local, new OutlineToggleMessage(true));
|
||||
_dragShadow = null;
|
||||
_draggables.Clear();
|
||||
_dragger = null;
|
||||
_mouseDownTime = 0;
|
||||
_savedMouseDown = null;
|
||||
}
|
||||
|
||||
private bool OnUseMouseUp(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (_dragDropHelper.IsDragging == false || _dragDropHelper.Dragged == null)
|
||||
{
|
||||
// haven't started the drag yet, quick mouseup, definitely treat it as a normal click by
|
||||
// replaying the original cmd
|
||||
if (_savedMouseDown.HasValue && _mouseDownTime < MaxMouseDownTimeForReplayingClick)
|
||||
{
|
||||
var savedValue = _savedMouseDown.Value;
|
||||
_isReplaying = true;
|
||||
// adjust the timing info based on the current tick so it appears as if it happened now
|
||||
var replayMsg = savedValue.OriginalMessage;
|
||||
var adjustedInputMsg = new FullInputCmdMessage(args.OriginalMessage.Tick, args.OriginalMessage.SubTick,
|
||||
replayMsg.InputFunctionId, replayMsg.State, replayMsg.Coordinates, replayMsg.ScreenCoordinates, replayMsg.Uid);
|
||||
|
||||
if (savedValue.Session != null)
|
||||
{
|
||||
_inputSystem.HandleInputCommand(savedValue.Session, EngineKeyFunctions.Use, adjustedInputMsg, true);
|
||||
}
|
||||
|
||||
_isReplaying = false;
|
||||
}
|
||||
_dragDropHelper.EndDrag();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_dragger == null)
|
||||
{
|
||||
_dragDropHelper.EndDrag();
|
||||
return false;
|
||||
}
|
||||
|
||||
// now when ending the drag, we will not replay the click because
|
||||
// by this time we've determined the input was actually a drag attempt
|
||||
var range = (args.Coordinates.ToMapPos(EntityManager) - _dragger.Transform.MapPosition.Position).Length + 0.01f;
|
||||
// tell the server we are dropping if we are over a valid drop target in range.
|
||||
// We don't use args.EntityUid here because drag interactions generally should
|
||||
// work even if there's something "on top" of the drop target
|
||||
if (!_interactionSystem.InRangeUnobstructed(_dragger,
|
||||
args.Coordinates, range, ignoreInsideBlocker: true))
|
||||
{
|
||||
_dragDropHelper.EndDrag();
|
||||
return false;
|
||||
}
|
||||
|
||||
var entities = GameScreenBase.GetEntitiesUnderPosition(_stateManager, args.Coordinates);
|
||||
var outOfRange = false;
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity == _dragDropHelper.Dragged) continue;
|
||||
|
||||
// check if it's able to be dropped on by current dragged entity
|
||||
var dropArgs = new DragDropEvent(_dragger, args.Coordinates, _dragDropHelper.Dragged, entity);
|
||||
|
||||
// TODO: Cache valid CanDragDrops
|
||||
if (ValidDragDrop(dropArgs) != true) continue;
|
||||
|
||||
if (!dropArgs.InRangeUnobstructed(ignoreInsideBlocker: true))
|
||||
{
|
||||
outOfRange = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var draggable in _draggables)
|
||||
{
|
||||
if (!draggable.CanDrop(dropArgs)) continue;
|
||||
|
||||
// tell the server about the drop attempt
|
||||
RaiseNetworkEvent(new DragDropRequestEvent(args.Coordinates, _dragDropHelper.Dragged!.Uid,
|
||||
entity.Uid));
|
||||
|
||||
draggable.Drop(dropArgs);
|
||||
|
||||
_dragDropHelper.EndDrag();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (outOfRange)
|
||||
{
|
||||
_playerManager.LocalPlayer?.ControlledEntity?.PopupMessage(Loc.GetString("You can't reach there!"));
|
||||
}
|
||||
|
||||
_dragDropHelper.EndDrag();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void HighlightTargets()
|
||||
{
|
||||
if (_dragDropHelper.Dragged == null ||
|
||||
_dragDropHelper.Dragged.Deleted ||
|
||||
_dragShadow == null ||
|
||||
_dragShadow.Deleted)
|
||||
{
|
||||
Logger.Warning("Programming error. Can't highlight drag and drop targets, not currently " +
|
||||
"dragging anything or dragged entity / shadow was deleted.");
|
||||
return;
|
||||
}
|
||||
|
||||
// highlights the possible targets which are visible
|
||||
// and able to be dropped on by the current dragged entity
|
||||
|
||||
// remove current highlights
|
||||
RemoveHighlights();
|
||||
|
||||
// find possible targets on screen even if not reachable
|
||||
// TODO: Duplicated in SpriteSystem
|
||||
var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition).Position;
|
||||
var bounds = new Box2(mousePos - 1.5f, mousePos + 1.5f);
|
||||
var pvsEntities = IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(_eyeManager.CurrentMap, bounds, true);
|
||||
foreach (var pvsEntity in pvsEntities)
|
||||
{
|
||||
if (!pvsEntity.TryGetComponent(out ISpriteComponent? inRangeSprite) ||
|
||||
!inRangeSprite.Visible ||
|
||||
pvsEntity == _dragDropHelper.Dragged) continue;
|
||||
|
||||
// check if it's able to be dropped on by current dragged entity
|
||||
var dropArgs = new DragDropEvent(_dragger!, pvsEntity.Transform.Coordinates, _dragDropHelper.Dragged, pvsEntity);
|
||||
|
||||
var valid = ValidDragDrop(dropArgs);
|
||||
if (valid == null) continue;
|
||||
|
||||
// We'll do a final check given server-side does this before any dragdrop can take place.
|
||||
if (valid.Value)
|
||||
{
|
||||
valid = dropArgs.InRangeUnobstructed(ignoreInsideBlocker: true);
|
||||
}
|
||||
|
||||
// highlight depending on whether its in or out of range
|
||||
inRangeSprite.PostShader = valid.Value ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
|
||||
inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value;
|
||||
_highlightedSprites.Add(inRangeSprite);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveHighlights()
|
||||
{
|
||||
foreach (var highlightedSprite in _highlightedSprites)
|
||||
{
|
||||
highlightedSprite.PostShader = null;
|
||||
highlightedSprite.RenderOrder = 0;
|
||||
}
|
||||
|
||||
_highlightedSprites.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Are these args valid for drag-drop?
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <returns>null if the target doesn't support IDragDropOn</returns>
|
||||
private bool? ValidDragDrop(DragDropEvent eventArgs)
|
||||
{
|
||||
bool? valid = null;
|
||||
|
||||
foreach (var comp in eventArgs.Target.GetAllComponents<IDragDropOn>())
|
||||
{
|
||||
if (!comp.CanDragDropOn(eventArgs))
|
||||
{
|
||||
valid = false;
|
||||
// dragDropOn.Add(comp);
|
||||
continue;
|
||||
}
|
||||
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (valid != true) return valid;
|
||||
|
||||
// Need at least one IDraggable to return true or else we can't do shit
|
||||
valid = false;
|
||||
|
||||
foreach (var comp in eventArgs.User.GetAllComponents<IDraggable>())
|
||||
{
|
||||
if (!comp.CanDrop(eventArgs)) continue;
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
_dragDropHelper.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ExamineSystem : ExamineSystemShared
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public const string StyleClassEntityTooltip = "entity-tooltip";
|
||||
|
||||
private Popup? _examineTooltipOpen;
|
||||
private CancellationTokenSource? _requestCancelTokenSource;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.ExamineEntity, new PointerInputCmdHandler(HandleExamine))
|
||||
.Register<ExamineSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<ExamineSystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private bool HandleExamine(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (!uid.IsValid() || !EntityManager.TryGetEntity(uid, out var examined))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (playerEntity == null || !CanExamine(playerEntity, examined))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DoExamine(examined);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async void DoExamine(IEntity entity)
|
||||
{
|
||||
const float minWidth = 300;
|
||||
CloseTooltip();
|
||||
|
||||
var popupPos = _userInterfaceManager.MousePositionScaled;
|
||||
|
||||
// Actually open the tooltip.
|
||||
_examineTooltipOpen = new Popup { MaxWidth = 400};
|
||||
_userInterfaceManager.ModalRoot.AddChild(_examineTooltipOpen);
|
||||
var panel = new PanelContainer();
|
||||
panel.AddStyleClass(StyleClassEntityTooltip);
|
||||
panel.ModulateSelfOverride = Color.LightGray.WithAlpha(0.90f);
|
||||
_examineTooltipOpen.AddChild(panel);
|
||||
//panel.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
|
||||
var vBox = new VBoxContainer();
|
||||
panel.AddChild(vBox);
|
||||
var hBox = new HBoxContainer {SeparationOverride = 5};
|
||||
vBox.AddChild(hBox);
|
||||
if (entity.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
hBox.AddChild(new SpriteView {Sprite = sprite, OverrideDirection = Direction.South});
|
||||
}
|
||||
|
||||
hBox.AddChild(new Label
|
||||
{
|
||||
Text = entity.Name,
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
|
||||
panel.Measure(Vector2.Infinity);
|
||||
var size = Vector2.ComponentMax((minWidth, 0), panel.DesiredSize);
|
||||
|
||||
_examineTooltipOpen.Open(UIBox2.FromDimensions(popupPos.Position, size));
|
||||
|
||||
FormattedMessage message;
|
||||
if (entity.Uid.IsClientSide())
|
||||
{
|
||||
message = GetExamineText(entity, _playerManager.LocalPlayer?.ControlledEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Ask server for extra examine info.
|
||||
RaiseNetworkEvent(new ExamineSystemMessages.RequestExamineInfoMessage(entity.Uid));
|
||||
|
||||
ExamineSystemMessages.ExamineInfoResponseMessage response;
|
||||
try
|
||||
{
|
||||
_requestCancelTokenSource = new CancellationTokenSource();
|
||||
response =
|
||||
await AwaitNetworkEvent<ExamineSystemMessages.ExamineInfoResponseMessage>(_requestCancelTokenSource
|
||||
.Token);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_requestCancelTokenSource = null;
|
||||
}
|
||||
|
||||
message = response.Message;
|
||||
}
|
||||
|
||||
foreach (var msg in message.Tags.OfType<FormattedMessage.TagText>())
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(msg.Text))
|
||||
{
|
||||
var richLabel = new RichTextLabel();
|
||||
richLabel.SetMessage(message);
|
||||
vBox.AddChild(richLabel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseTooltip()
|
||||
{
|
||||
if (_examineTooltipOpen != null)
|
||||
{
|
||||
_examineTooltipOpen.Dispose();
|
||||
_examineTooltipOpen = null;
|
||||
}
|
||||
|
||||
if (_requestCancelTokenSource != null)
|
||||
{
|
||||
_requestCancelTokenSource.Cancel();
|
||||
_requestCancelTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.HealthOverlay
|
||||
{
|
||||
public class HealthOverlayBar : Control
|
||||
{
|
||||
public const byte HealthBarScale = 2;
|
||||
|
||||
private const int XPixelDiff = 20 * HealthBarScale;
|
||||
|
||||
public HealthOverlayBar()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
Shader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
private ShaderInstance Shader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// From -1 (dead) to 0 (crit) and 1 (alive)
|
||||
/// </summary>
|
||||
public float Ratio { get; set; }
|
||||
|
||||
public Color Color { get; set; }
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
handle.UseShader(Shader);
|
||||
|
||||
var leftOffset = 2 * HealthBarScale;
|
||||
var box = new UIBox2i(
|
||||
leftOffset,
|
||||
-2 + 2 * HealthBarScale,
|
||||
leftOffset + (int) (XPixelDiff * Ratio * UIScale),
|
||||
-2);
|
||||
|
||||
handle.DrawRect(box, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
#nullable enable
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.HealthOverlay
|
||||
{
|
||||
public class HealthOverlayGui : VBoxContainer
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
public HealthOverlayGui(IEntity entity)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(this);
|
||||
SeparationOverride = 0;
|
||||
|
||||
CritBar = new HealthOverlayBar
|
||||
{
|
||||
Visible = false,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Color = Color.Red
|
||||
};
|
||||
|
||||
HealthBar = new HealthOverlayBar
|
||||
{
|
||||
Visible = false,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Color = Color.Green
|
||||
};
|
||||
|
||||
AddChild(Panel = new PanelContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/Misc/health_bar.rsi/icon.png"),
|
||||
TextureScale = Vector2.One * HealthOverlayBar.HealthBarScale,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
},
|
||||
CritBar,
|
||||
HealthBar
|
||||
}
|
||||
});
|
||||
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public PanelContainer Panel { get; }
|
||||
|
||||
public HealthOverlayBar HealthBar { get; }
|
||||
|
||||
public HealthOverlayBar CritBar { get; }
|
||||
|
||||
public IEntity Entity { get; }
|
||||
|
||||
public void SetVisibility(bool val)
|
||||
{
|
||||
Visible = val;
|
||||
Panel.Visible = val;
|
||||
}
|
||||
|
||||
private void MoreFrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (Entity.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Entity.TryGetComponent(out IMobStateComponent? mobState) ||
|
||||
!Entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
CritBar.Visible = false;
|
||||
HealthBar.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int threshold;
|
||||
|
||||
if (mobState.IsAlive())
|
||||
{
|
||||
if (!mobState.TryGetEarliestCriticalState(damageable.TotalDamage, out _, out threshold))
|
||||
{
|
||||
CritBar.Visible = false;
|
||||
HealthBar.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
CritBar.Ratio = 1;
|
||||
CritBar.Visible = true;
|
||||
HealthBar.Ratio = 1 - (float) damageable.TotalDamage / threshold;
|
||||
HealthBar.Visible = true;
|
||||
}
|
||||
else if (mobState.IsCritical())
|
||||
{
|
||||
HealthBar.Ratio = 0;
|
||||
HealthBar.Visible = false;
|
||||
|
||||
if (!mobState.TryGetPreviousCriticalState(damageable.TotalDamage, out _, out var critThreshold) ||
|
||||
!mobState.TryGetEarliestDeadState(damageable.TotalDamage, out _, out var deadThreshold))
|
||||
{
|
||||
CritBar.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
CritBar.Visible = true;
|
||||
CritBar.Ratio = 1 - (float)
|
||||
(damageable.TotalDamage - critThreshold) /
|
||||
(deadThreshold - critThreshold);
|
||||
}
|
||||
else if (mobState.IsDead())
|
||||
{
|
||||
CritBar.Ratio = 0;
|
||||
CritBar.Visible = false;
|
||||
HealthBar.Ratio = 0;
|
||||
HealthBar.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CritBar.Visible = false;
|
||||
HealthBar.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
MoreFrameUpdate(args);
|
||||
|
||||
if (Entity.Deleted ||
|
||||
_eyeManager.CurrentMap != Entity.Transform.MapID)
|
||||
{
|
||||
Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Visible = true;
|
||||
|
||||
var screenCoordinates = _eyeManager.CoordinatesToScreen(Entity.Transform.Coordinates);
|
||||
var playerPosition = UserInterfaceManager.ScreenToUIPosition(screenCoordinates);
|
||||
LayoutContainer.SetPosition(this, new Vector2(playerPosition.X - Width / 2, playerPosition.Y - Height - 30.0f));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing) return;
|
||||
|
||||
HealthBar.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameTicking;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems.HealthOverlay
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class HealthOverlaySystem : EntitySystem, IResettingEntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private readonly Dictionary<EntityUid, HealthOverlayGui> _guis = new();
|
||||
private IEntity? _attachedEntity;
|
||||
private bool _enabled;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enabled = value;
|
||||
|
||||
foreach (var gui in _guis.Values)
|
||||
{
|
||||
gui.SetVisibility(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PlayerAttachSysMessage>(HandlePlayerAttached);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
UnsubscribeLocalEvent<PlayerAttachSysMessage>();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
foreach (var gui in _guis.Values)
|
||||
{
|
||||
gui.Dispose();
|
||||
}
|
||||
|
||||
_guis.Clear();
|
||||
_attachedEntity = null;
|
||||
}
|
||||
|
||||
private void HandlePlayerAttached(PlayerAttachSysMessage message)
|
||||
{
|
||||
_attachedEntity = message.AttachedEntity;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_attachedEntity == null || _attachedEntity.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var viewBox = _eyeManager.GetWorldViewport().Enlarged(2.0f);
|
||||
|
||||
foreach (var (mobState, _) in ComponentManager.EntityQuery<IMobStateComponent, IDamageableComponent>())
|
||||
{
|
||||
var entity = mobState.Owner;
|
||||
|
||||
if (_attachedEntity.Transform.MapID != entity.Transform.MapID ||
|
||||
!viewBox.Contains(entity.Transform.WorldPosition))
|
||||
{
|
||||
if (_guis.TryGetValue(entity.Uid, out var oldGui))
|
||||
{
|
||||
oldGui.Dispose();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_guis.ContainsKey(entity.Uid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gui = new HealthOverlayGui(entity);
|
||||
_guis.Add(entity.Uid, gui);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components.IconSmoothing;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity system implementing the logic for <see cref="IconSmoothComponent"/>
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal sealed class IconSmoothSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private readonly Queue<EntityUid> _dirtyEntities = new();
|
||||
|
||||
private int _generation;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<IconSmoothDirtyEvent>(HandleDirtyEvent);
|
||||
|
||||
SubscribeLocalEvent<IconSmoothComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
}
|
||||
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
UnsubscribeLocalEvent<IconSmoothDirtyEvent>();
|
||||
|
||||
UnsubscribeLocalEvent<IconSmoothComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
if (_dirtyEntities.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_generation += 1;
|
||||
|
||||
// Performance: This could be spread over multiple updates, or made parallel.
|
||||
while (_dirtyEntities.Count > 0)
|
||||
{
|
||||
CalculateNewSprite(_dirtyEntities.Dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDirtyEvent(IconSmoothDirtyEvent ev)
|
||||
{
|
||||
// Yes, we updates ALL smoothing entities surrounding us even if they would never smooth with us.
|
||||
// This is simpler to implement. If you want to optimize it be my guest.
|
||||
var senderEnt = ev.Sender;
|
||||
if (senderEnt.IsValid() &&
|
||||
_mapManager.TryGetGrid(senderEnt.Transform.GridID, out var grid1) &&
|
||||
senderEnt.TryGetComponent(out IconSmoothComponent? iconSmooth)
|
||||
&& iconSmooth.Running)
|
||||
{
|
||||
var coords = senderEnt.Transform.Coordinates;
|
||||
|
||||
_dirtyEntities.Enqueue(senderEnt.Uid);
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.North));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.South));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.East));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.West));
|
||||
if (ev.Mode == IconSmoothingMode.Corners)
|
||||
{
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.NorthEast));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.SouthEast));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.SouthWest));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.NorthWest));
|
||||
}
|
||||
}
|
||||
|
||||
// Entity is no longer valid, update around the last position it was at.
|
||||
if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
|
||||
{
|
||||
var pos = ev.LastPosition.Value.pos;
|
||||
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, 1)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, -1)));
|
||||
if (ev.Mode == IconSmoothingMode.Corners)
|
||||
{
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, 1)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, -1)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, 1)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, -1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleSnapGridMove(EntityUid uid, IconSmoothComponent component, SnapGridPositionChangedEvent args)
|
||||
{
|
||||
component.SnapGridOnPositionChanged();
|
||||
}
|
||||
|
||||
private void AddValidEntities(IEnumerable<EntityUid> candidates)
|
||||
{
|
||||
foreach (var entity in candidates)
|
||||
{
|
||||
if (ComponentManager.HasComponent<IconSmoothComponent>(entity))
|
||||
{
|
||||
_dirtyEntities.Enqueue(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateNewSprite(EntityUid euid)
|
||||
{
|
||||
// The generation check prevents updating an entity multiple times per tick.
|
||||
// As it stands now, it's totally possible for something to get queued twice.
|
||||
// Generation on the component is set after an update so we can cull updates that happened this generation.
|
||||
if (!EntityManager.EntityExists(euid)
|
||||
|| !ComponentManager.TryGetComponent(euid, out IconSmoothComponent? smoothing)
|
||||
|| smoothing.UpdateGeneration == _generation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
smoothing.CalculateNewSprite();
|
||||
|
||||
smoothing.UpdateGeneration = _generation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised by a <see cref="IconSmoothComponent"/> when it needs to be recalculated.
|
||||
/// </summary>
|
||||
public sealed class IconSmoothDirtyEvent : EntityEventArgs
|
||||
{
|
||||
public IconSmoothDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, IconSmoothingMode mode)
|
||||
{
|
||||
LastPosition = lastPosition;
|
||||
Mode = mode;
|
||||
Sender = sender;
|
||||
}
|
||||
|
||||
public (GridId grid, Vector2i pos)? LastPosition { get; }
|
||||
public IconSmoothingMode Mode { get; }
|
||||
public IEntity Sender { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Instruments;
|
||||
using Content.Shared;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class InstrumentSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_cfg.OnValueChanged(CCVars.MaxMidiEventsPerBatch, OnMaxMidiEventsPerBatchChanged, true);
|
||||
_cfg.OnValueChanged(CCVars.MaxMidiEventsPerSecond, OnMaxMidiEventsPerSecondChanged, true);
|
||||
}
|
||||
|
||||
public int MaxMidiEventsPerBatch { get; private set; }
|
||||
public int MaxMidiEventsPerSecond { get; private set; }
|
||||
|
||||
private void OnMaxMidiEventsPerSecondChanged(int obj)
|
||||
{
|
||||
MaxMidiEventsPerSecond = obj;
|
||||
}
|
||||
|
||||
private void OnMaxMidiEventsPerBatchChanged(int obj)
|
||||
{
|
||||
MaxMidiEventsPerBatch = obj;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var instrumentComponent in EntityManager.ComponentManager.EntityQuery<InstrumentComponent>(true))
|
||||
{
|
||||
instrumentComponent.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Markers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public sealed class MarkerSystem : EntitySystem
|
||||
{
|
||||
private bool _markersVisible;
|
||||
|
||||
public bool MarkersVisible
|
||||
{
|
||||
get => _markersVisible;
|
||||
set
|
||||
{
|
||||
_markersVisible = value;
|
||||
UpdateMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMarkers()
|
||||
{
|
||||
foreach (var markerComponent in EntityManager.ComponentManager.EntityQuery<MarkerComponent>(true))
|
||||
{
|
||||
markerComponent.UpdateVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class MeleeLungeSystem : EntitySystem
|
||||
{
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var meleeLungeComponent in EntityManager.ComponentManager.EntityQuery<MeleeLungeComponent>(true))
|
||||
{
|
||||
meleeLungeComponent.Update(frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using Content.Client.GameObjects.Components.Weapons.Melee;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Melee;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Shared.GameObjects.EntitySystemMessages.MeleeWeaponSystemMessages;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class MeleeWeaponSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeNetworkEvent<PlayMeleeWeaponAnimationMessage>(PlayWeaponArc);
|
||||
SubscribeNetworkEvent<PlayLungeAnimationMessage>(PlayLunge);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var arcAnimationComponent in EntityManager.ComponentManager.EntityQuery<MeleeWeaponArcAnimationComponent>(true))
|
||||
{
|
||||
arcAnimationComponent.Update(frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayWeaponArc(PlayMeleeWeaponAnimationMessage msg)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(msg.ArcPrototype, out MeleeWeaponAnimationPrototype? weaponArc))
|
||||
{
|
||||
Logger.Error("Tried to play unknown weapon arc prototype '{0}'", msg.ArcPrototype);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetEntity(msg.Attacker, out var attacker))
|
||||
{
|
||||
// FIXME: This should never happen.
|
||||
Logger.Error($"Tried to play a weapon arc {msg.ArcPrototype}, but the attacker does not exist. attacker={msg.Attacker}, source={msg.Source}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!attacker.Deleted)
|
||||
{
|
||||
var lunge = attacker.EnsureComponent<MeleeLungeComponent>();
|
||||
lunge.SetData(msg.Angle);
|
||||
|
||||
var entity = EntityManager.SpawnEntity(weaponArc.Prototype, attacker.Transform.Coordinates);
|
||||
entity.Transform.LocalRotation = msg.Angle;
|
||||
|
||||
var weaponArcAnimation = entity.GetComponent<MeleeWeaponArcAnimationComponent>();
|
||||
weaponArcAnimation.SetData(weaponArc, msg.Angle, attacker, msg.ArcFollowAttacker);
|
||||
|
||||
// Due to ISpriteComponent limitations, weapons that don't use an RSI won't have this effect.
|
||||
if (EntityManager.TryGetEntity(msg.Source, out var source) &&
|
||||
msg.TextureEffect &&
|
||||
source.TryGetComponent(out ISpriteComponent? sourceSprite) &&
|
||||
sourceSprite.BaseRSI?.Path != null)
|
||||
{
|
||||
var sys = Get<EffectSystem>();
|
||||
var curTime = _gameTiming.CurTime;
|
||||
var effect = new EffectSystemMessage
|
||||
{
|
||||
EffectSprite = sourceSprite.BaseRSI.Path.ToString(),
|
||||
RsiState = sourceSprite.LayerGetState(0).Name,
|
||||
Coordinates = attacker.Transform.Coordinates,
|
||||
Color = Vector4.Multiply(new Vector4(255, 255, 255, 125), 1.0f),
|
||||
ColorDelta = Vector4.Multiply(new Vector4(0, 0, 0, -10), 1.0f),
|
||||
Velocity = msg.Angle.ToWorldVec(),
|
||||
Acceleration = msg.Angle.ToWorldVec() * 5f,
|
||||
Born = curTime,
|
||||
DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)),
|
||||
};
|
||||
sys.CreateEffect(effect);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var uid in msg.Hits)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(uid, out var hitEntity) || hitEntity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hitEntity.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var originalColor = sprite.Color;
|
||||
var newColor = Color.Red * originalColor;
|
||||
sprite.Color = newColor;
|
||||
|
||||
hitEntity.SpawnTimer(100, () =>
|
||||
{
|
||||
// Only reset back to the original color if something else didn't change the color in the mean time.
|
||||
if (sprite.Color == newColor)
|
||||
{
|
||||
sprite.Color = originalColor;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayLunge(PlayLungeAnimationMessage msg)
|
||||
{
|
||||
if (EntityManager.TryGetEntity(msg.Source, out var entity))
|
||||
{
|
||||
entity.EnsureComponent<MeleeLungeComponent>().SetData(msg.Angle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: This should never happen.
|
||||
Logger.Error($"Tried to play a lunge animation, but the entity \"{msg.Source}\" does not exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Client.GameObjects.Components.Pulling;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages.Pulling;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Physics;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PullingSystem : SharedPullingSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
SubscribeLocalEvent<PullableComponent, PullableMoveMessage>(OnPullableMove);
|
||||
SubscribeLocalEvent<PullableComponent, PullableStopMovingMessage>(OnPullableStopMove);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
UnsubscribeLocalEvent<PullableComponent, PullableMoveMessage>(OnPullableMove);
|
||||
UnsubscribeLocalEvent<PullableComponent, PullableStopMovingMessage>(OnPullableStopMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
using System;
|
||||
using Content.Client.GameObjects.Components.Items;
|
||||
using Content.Client.GameObjects.Components.Weapons.Ranged;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class RangedWeaponSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private InputSystem _inputSystem = default!;
|
||||
private CombatModeSystem _combatModeSystem = default!;
|
||||
private bool _blocked;
|
||||
private int _shotCounter;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
_inputSystem = Get<InputSystem>();
|
||||
_combatModeSystem = Get<CombatModeSystem>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
|
||||
if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down)
|
||||
{
|
||||
_shotCounter = 0;
|
||||
_blocked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var entity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (entity == null || !entity.TryGetComponent(out HandsComponent? hands))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var held = hands.ActiveHand;
|
||||
if (held == null || !held.TryGetComponent(out ClientRangedWeaponComponent? weapon))
|
||||
{
|
||||
_blocked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (weapon.FireRateSelector)
|
||||
{
|
||||
case FireRateSelector.Safety:
|
||||
_blocked = true;
|
||||
return;
|
||||
case FireRateSelector.Single:
|
||||
if (_shotCounter >= 1)
|
||||
{
|
||||
_blocked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case FireRateSelector.Automatic:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (_blocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var worldPos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition);
|
||||
|
||||
if (!_mapManager.TryFindGridAt(worldPos, out var grid))
|
||||
{
|
||||
weapon.SyncFirePos(GridId.Invalid, worldPos.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
weapon.SyncFirePos(grid.Index, grid.MapToGrid(worldPos).Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Content.Client.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class StackSystem : SharedStackSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StackComponent, StackCountChangedEvent>(OnStackCountChanged);
|
||||
}
|
||||
|
||||
private void OnStackCountChanged(EntityUid uid, StackComponent component, StackCountChangedEvent args)
|
||||
{
|
||||
// Dirty the UI now that the stack count has changed.
|
||||
component.DirtyUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using Content.Shared.GameObjects.Components.Rotation;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public class StandingStateSystem : SharedStandingStateSystem
|
||||
{
|
||||
protected override bool OnDown(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false)
|
||||
{
|
||||
if (!entity.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var newState = RotationState.Horizontal;
|
||||
appearance.TryGetData<RotationState>(RotationVisuals.RotationState, out var oldState);
|
||||
|
||||
if (newState != oldState)
|
||||
{
|
||||
appearance.SetData(RotationVisuals.RotationState, newState);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnStand(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out AppearanceComponent? appearance)) return false;
|
||||
|
||||
appearance.TryGetData<RotationState>(RotationVisuals.RotationState, out var oldState);
|
||||
var newState = RotationState.Vertical;
|
||||
|
||||
if (newState == oldState) return false;
|
||||
|
||||
appearance.SetData(RotationVisuals.RotationState, newState);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public sealed class SuspicionEndTimerSystem : EntitySystem
|
||||
{
|
||||
public TimeSpan? EndTime { get; private set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<SuspicionMessages.SetSuspicionEndTimerMessage>(RxTimerMessage);
|
||||
}
|
||||
|
||||
private void RxTimerMessage(SuspicionMessages.SetSuspicionEndTimerMessage ev)
|
||||
{
|
||||
EndTime = ev.EndTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class VerbSystem : SharedVerbSystem, IResettingEntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
|
||||
public event EventHandler<PointerInputCmdHandler.PointerInputCmdArgs>? ToggleContextMenu;
|
||||
public event EventHandler<bool>? ToggleContainerVisibility;
|
||||
|
||||
private ContextMenuPresenter _contextMenuPresenter = default!;
|
||||
private VerbPopup? _currentVerbListRoot;
|
||||
private VerbPopup? _currentGroupList;
|
||||
private EntityUid _currentEntity;
|
||||
|
||||
// TODO: Move presenter out of the system
|
||||
// TODO: Separate the rest of the UI from the logic
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<VerbSystemMessages.VerbsResponseMessage>(FillEntityPopup);
|
||||
SubscribeNetworkEvent<PlayerContainerVisibilityMessage>(HandleContainerVisibilityMessage);
|
||||
|
||||
_contextMenuPresenter = new ContextMenuPresenter(this);
|
||||
SubscribeLocalEvent<MoveEvent>(_contextMenuPresenter.HandleMoveEvent);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenContextMenu,
|
||||
new PointerInputCmdHandler(HandleOpenContextMenu))
|
||||
.Register<VerbSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
UnsubscribeNetworkEvent<VerbSystemMessages.VerbsResponseMessage>();
|
||||
UnsubscribeNetworkEvent<PlayerContainerVisibilityMessage>();
|
||||
UnsubscribeLocalEvent<MoveEvent>();
|
||||
_contextMenuPresenter?.Dispose();
|
||||
|
||||
CommandBinds.Unregister<VerbSystem>();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ToggleContainerVisibility?.Invoke(this, false);
|
||||
}
|
||||
|
||||
private bool HandleOpenContextMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (args.State == BoundKeyState.Down)
|
||||
{
|
||||
ToggleContextMenu?.Invoke(this, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private void HandleContainerVisibilityMessage(PlayerContainerVisibilityMessage ev)
|
||||
{
|
||||
ToggleContainerVisibility?.Invoke(this, ev.CanSeeThrough);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
_contextMenuPresenter?.Update();
|
||||
}
|
||||
|
||||
public void OpenContextMenu(IEntity entity, ScreenCoordinates screenCoordinates)
|
||||
{
|
||||
if (_currentVerbListRoot != null)
|
||||
{
|
||||
CloseVerbMenu();
|
||||
}
|
||||
|
||||
_currentEntity = entity.Uid;
|
||||
_currentVerbListRoot = new VerbPopup();
|
||||
_userInterfaceManager.ModalRoot.AddChild(_currentVerbListRoot);
|
||||
_currentVerbListRoot.OnPopupHide += CloseVerbMenu;
|
||||
|
||||
if (!entity.Uid.IsClientSide())
|
||||
{
|
||||
_currentVerbListRoot.List.AddChild(new Label { Text = Loc.GetString("Waiting on Server...") });
|
||||
RaiseNetworkEvent(new VerbSystemMessages.RequestVerbsMessage(_currentEntity));
|
||||
}
|
||||
|
||||
var box = UIBox2.FromDimensions(screenCoordinates.Position, (1, 1));
|
||||
_currentVerbListRoot.Open(box);
|
||||
}
|
||||
|
||||
public void OnContextButtonPressed(IEntity entity)
|
||||
{
|
||||
OpenContextMenu(entity, _userInterfaceManager.MousePositionScaled);
|
||||
}
|
||||
|
||||
private void FillEntityPopup(VerbSystemMessages.VerbsResponseMessage msg)
|
||||
{
|
||||
if (_currentEntity != msg.Entity || !EntityManager.TryGetEntity(_currentEntity, out var entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_currentVerbListRoot);
|
||||
|
||||
var buttons = new Dictionary<string, List<ListedVerbData>>();
|
||||
var groupIcons = new Dictionary<string, SpriteSpecifier>();
|
||||
|
||||
var vBox = _currentVerbListRoot!.List;
|
||||
vBox.DisposeAllChildren();
|
||||
|
||||
// Local variable so that scope capture ensures this is the correct value.
|
||||
var curEntity = _currentEntity;
|
||||
|
||||
foreach (var data in msg.Verbs)
|
||||
{
|
||||
var list = buttons.GetOrNew(data.Category);
|
||||
|
||||
if (data.CategoryIcon != null && !groupIcons.ContainsKey(data.Category))
|
||||
{
|
||||
groupIcons.Add(data.Category, data.CategoryIcon);
|
||||
}
|
||||
|
||||
list.Add(new ListedVerbData(data.Text, !data.Available, data.Key, entity.ToString()!, () =>
|
||||
{
|
||||
RaiseNetworkEvent(new VerbSystemMessages.UseVerbMessage(curEntity, data.Key));
|
||||
CloseAllMenus();
|
||||
}, data.Icon));
|
||||
}
|
||||
|
||||
var user = GetUserEntity();
|
||||
//Get verbs, component dependent.
|
||||
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
|
||||
{
|
||||
if (!VerbUtility.VerbAccessChecks(user, entity, verb))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var verbData = verb.GetData(user, component);
|
||||
|
||||
if (verbData.IsInvisible)
|
||||
continue;
|
||||
|
||||
var list = buttons.GetOrNew(verbData.Category);
|
||||
|
||||
if (verbData.CategoryIcon != null && !groupIcons.ContainsKey(verbData.Category))
|
||||
{
|
||||
groupIcons.Add(verbData.Category, verbData.CategoryIcon);
|
||||
}
|
||||
|
||||
list.Add(new ListedVerbData(verbData.Text, verbData.IsDisabled, verb.ToString()!, entity.ToString()!,
|
||||
() => verb.Activate(user, component), verbData.Icon));
|
||||
}
|
||||
|
||||
//Get global verbs. Visible for all entities regardless of their components.
|
||||
foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
|
||||
{
|
||||
if (!VerbUtility.VerbAccessChecks(user, entity, globalVerb))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var verbData = globalVerb.GetData(user, entity);
|
||||
|
||||
if (verbData.IsInvisible)
|
||||
continue;
|
||||
|
||||
var list = buttons.GetOrNew(verbData.Category);
|
||||
|
||||
if (verbData.CategoryIcon != null && !groupIcons.ContainsKey(verbData.Category))
|
||||
{
|
||||
groupIcons.Add(verbData.Category, verbData.CategoryIcon);
|
||||
}
|
||||
|
||||
list.Add(new ListedVerbData(verbData.Text, verbData.IsDisabled, globalVerb.ToString()!,
|
||||
entity.ToString()!,
|
||||
() => globalVerb.Activate(user, entity), verbData.Icon));
|
||||
}
|
||||
|
||||
if (buttons.Count > 0)
|
||||
{
|
||||
var first = true;
|
||||
foreach (var (category, verbs) in buttons)
|
||||
{
|
||||
if (string.IsNullOrEmpty(category))
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
{
|
||||
vBox.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#333") }
|
||||
});
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
groupIcons.TryGetValue(category, out var icon);
|
||||
|
||||
vBox.AddChild(CreateCategoryButton(category, verbs, icon));
|
||||
}
|
||||
|
||||
if (buttons.ContainsKey(""))
|
||||
{
|
||||
buttons[""].Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.CurrentCulture));
|
||||
|
||||
foreach (var verb in buttons[""])
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
vBox.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#333") }
|
||||
});
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
vBox.AddChild(CreateVerbButton(verb));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var panel = new PanelContainer();
|
||||
panel.AddChild(new Label { Text = Loc.GetString("No verbs!") });
|
||||
vBox.AddChild(panel);
|
||||
}
|
||||
}
|
||||
|
||||
private VerbButton CreateVerbButton(ListedVerbData data)
|
||||
{
|
||||
var button = new VerbButton
|
||||
{
|
||||
Text = Loc.GetString(data.Text),
|
||||
Disabled = data.Disabled
|
||||
};
|
||||
|
||||
if (data.Icon != null)
|
||||
{
|
||||
button.Icon = data.Icon.Frame0();
|
||||
}
|
||||
|
||||
if (!data.Disabled)
|
||||
{
|
||||
button.OnPressed += _ =>
|
||||
{
|
||||
CloseAllMenus();
|
||||
try
|
||||
{
|
||||
data.Action.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("verb", "Exception in verb {0} on {1}:\n{2}", data.VerbName, data.OwnerName, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private Control CreateCategoryButton(string text, List<ListedVerbData> verbButtons, SpriteSpecifier? icon)
|
||||
{
|
||||
verbButtons.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.CurrentCulture));
|
||||
|
||||
return new VerbGroupButton(this, verbButtons, icon)
|
||||
{
|
||||
Text = Loc.GetString(text),
|
||||
};
|
||||
}
|
||||
|
||||
public void CloseVerbMenu()
|
||||
{
|
||||
_currentVerbListRoot?.Dispose();
|
||||
_currentVerbListRoot = null;
|
||||
_currentEntity = EntityUid.Invalid;
|
||||
}
|
||||
|
||||
private void CloseAllMenus()
|
||||
{
|
||||
CloseVerbMenu();
|
||||
// CloseContextPopups();
|
||||
CloseGroupMenu();
|
||||
}
|
||||
|
||||
public void CloseGroupMenu()
|
||||
{
|
||||
_currentGroupList?.Dispose();
|
||||
_currentGroupList = null;
|
||||
}
|
||||
|
||||
private IEntity GetUserEntity()
|
||||
{
|
||||
return _playerManager.LocalPlayer!.ControlledEntity!;
|
||||
}
|
||||
|
||||
private sealed class VerbPopup : Popup
|
||||
{
|
||||
public VBoxContainer List { get; }
|
||||
|
||||
public VerbPopup()
|
||||
{
|
||||
AddChild(new PanelContainer
|
||||
{
|
||||
Children = {(List = new VBoxContainer())},
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#111E")}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VerbButton : BaseButton
|
||||
{
|
||||
private readonly Label _label;
|
||||
private readonly TextureRect _icon;
|
||||
|
||||
public Texture? Icon
|
||||
{
|
||||
get => _icon.Texture;
|
||||
set => _icon.Texture = value;
|
||||
}
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => _label.Text;
|
||||
set => _label.Text = value;
|
||||
}
|
||||
|
||||
public VerbButton()
|
||||
{
|
||||
AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_icon = new TextureRect
|
||||
{
|
||||
MinSize = (32, 32),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
TextureScale = (0.5f, 0.5f)
|
||||
}),
|
||||
(_label = new Label()),
|
||||
// Padding
|
||||
new Control {MinSize = (8, 0)}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (DrawMode == DrawModeEnum.Hover)
|
||||
{
|
||||
handle.DrawRect(PixelSizeBox, Color.DarkSlateGray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VerbGroupButton : Control
|
||||
{
|
||||
private static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
||||
|
||||
private readonly VerbSystem _system;
|
||||
|
||||
private readonly Label _label;
|
||||
private readonly TextureRect _icon;
|
||||
|
||||
private CancellationTokenSource? _openCancel;
|
||||
|
||||
public List<ListedVerbData> VerbButtons { get; }
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => _label.Text;
|
||||
set => _label.Text = value;
|
||||
}
|
||||
|
||||
public Texture? Icon
|
||||
{
|
||||
get => _icon.Texture;
|
||||
set => _icon.Texture = value;
|
||||
}
|
||||
|
||||
public VerbGroupButton(VerbSystem system, List<ListedVerbData> verbButtons, SpriteSpecifier? icon)
|
||||
{
|
||||
_system = system;
|
||||
VerbButtons = verbButtons;
|
||||
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_icon = new TextureRect
|
||||
{
|
||||
MinSize = (32, 32),
|
||||
TextureScale = (0.5f, 0.5f),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
}),
|
||||
|
||||
(_label = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
}),
|
||||
|
||||
// Padding
|
||||
new Control {MinSize = (8, 0)},
|
||||
|
||||
new TextureRect
|
||||
{
|
||||
Texture = IoCManager.Resolve<IResourceCache>()
|
||||
.GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"),
|
||||
TextureScale = (0.5f, 0.5f),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (icon != null)
|
||||
{
|
||||
_icon.Texture = icon.Frame0();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (this == UserInterfaceManager.CurrentlyHovered)
|
||||
{
|
||||
handle.DrawRect(PixelSizeBox, Color.DarkSlateGray);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void MouseEntered()
|
||||
{
|
||||
base.MouseEntered();
|
||||
|
||||
_openCancel = new CancellationTokenSource();
|
||||
|
||||
Timer.Spawn(HoverDelay, () =>
|
||||
{
|
||||
if (_system._currentGroupList != null)
|
||||
{
|
||||
_system.CloseGroupMenu();
|
||||
}
|
||||
|
||||
var popup = _system._currentGroupList = new VerbPopup();
|
||||
|
||||
var first = true;
|
||||
foreach (var verb in VerbButtons)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
popup.List.AddChild(new PanelContainer
|
||||
{
|
||||
MinSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#333")}
|
||||
});
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
popup.List.AddChild(_system.CreateVerbButton(verb));
|
||||
}
|
||||
|
||||
UserInterfaceManager.ModalRoot.AddChild(popup);
|
||||
popup.Open(UIBox2.FromDimensions(GlobalPosition + (Width, 0), (1, 1)), GlobalPosition);
|
||||
}, _openCancel.Token);
|
||||
}
|
||||
|
||||
protected override void MouseExited()
|
||||
{
|
||||
base.MouseExited();
|
||||
|
||||
_openCancel?.Cancel();
|
||||
_openCancel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ListedVerbData
|
||||
{
|
||||
public string Text { get; }
|
||||
public bool Disabled { get; }
|
||||
public string VerbName { get; }
|
||||
public string OwnerName { get; }
|
||||
public SpriteSpecifier? Icon { get; }
|
||||
public Action Action { get; }
|
||||
|
||||
public ListedVerbData(string text, bool disabled, string verbName, string ownerName,
|
||||
Action action, SpriteSpecifier? icon)
|
||||
{
|
||||
Text = text;
|
||||
Disabled = disabled;
|
||||
VerbName = verbName;
|
||||
OwnerName = ownerName;
|
||||
Action = action;
|
||||
Icon = icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class WindowSystem : EntitySystem
|
||||
{
|
||||
private readonly Queue<IEntity> _dirtyEntities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<WindowSmoothDirtyEvent>(HandleDirtyEvent);
|
||||
SubscribeLocalEvent<WindowComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
UnsubscribeLocalEvent<WindowSmoothDirtyEvent>();
|
||||
UnsubscribeLocalEvent<WindowComponent, SnapGridPositionChangedEvent>(HandleSnapGridMove);
|
||||
}
|
||||
|
||||
private void HandleDirtyEvent(WindowSmoothDirtyEvent ev)
|
||||
{
|
||||
if (ev.Sender.HasComponent<WindowComponent>())
|
||||
{
|
||||
_dirtyEntities.Enqueue(ev.Sender);
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleSnapGridMove(EntityUid uid, WindowComponent component, SnapGridPositionChangedEvent args)
|
||||
{
|
||||
component.SnapGridOnPositionChanged();
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
// Performance: This could be spread over multiple updates, or made parallel.
|
||||
while (_dirtyEntities.Count > 0)
|
||||
{
|
||||
var entity = _dirtyEntities.Dequeue();
|
||||
if (entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
entity.GetComponent<WindowComponent>().UpdateSprite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised by a <see cref="WindowComponent"/> when it needs to be recalculated.
|
||||
/// </summary>
|
||||
public sealed class WindowSmoothDirtyEvent : EntityEventArgs
|
||||
{
|
||||
public IEntity Sender { get; }
|
||||
|
||||
public WindowSmoothDirtyEvent(IEntity sender)
|
||||
{
|
||||
Sender = sender;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user