Files
wwdpublic/Content.Client/Construction/ConstructionSystem.cs
SimpleStation14 3b2a19d9ec Mirror: Code cleanup: Purge calls to obsolete EntityCoordinates methods (#289)
## Mirror of PR #26292: [Code cleanup: Purge calls to obsolete
EntityCoordinates
methods](https://github.com/space-wizards/space-station-14/pull/26292)
from <img src="https://avatars.githubusercontent.com/u/10567778?v=4"
alt="space-wizards" width="22"/>
[space-wizards](https://github.com/space-wizards)/[space-station-14](https://github.com/space-wizards/space-station-14)

###### `f4cb02fb0ca385c858569c07c51afb0d24ade949`

PR opened by <img
src="https://avatars.githubusercontent.com/u/85356?v=4" width="16"/><a
href="https://github.com/Tayrtahn"> Tayrtahn</a> at 2024-03-20 16:04:43
UTC

---

PR changed 34 files with 70 additions and 56 deletions.

The PR had the following labels:
- Status: Needs Review


---

<details open="true"><summary><h1>Original Body</h1></summary>

> <!-- Please read these guidelines before opening your PR:
https://docs.spacestation14.io/en/getting-started/pr-guideline -->
> <!-- The text between the arrows are comments - they will not be
visible on your PR. -->
> 
> ## About the PR
> <!-- What did you change in this PR? -->
> Cleaned up some outdated code.
> 
> ## Why / Balance
> <!-- Why was it changed? Link any discussions or issues here. Please
discuss how this would affect game balance. -->
> Clean code is happy code.
> 
> ## Technical details
> <!-- If this is a code change, summarize at high level how your new
code works. This makes it easier to review. -->
> Updated all calls to obsolete EntityCoordinates methods (ToMap,
ToMapPos, FromMap, ToVector2i, InRange) to non-obsolete ones (by passing
in SharedTransformSystem as an arg).
> 
> ## Media
> <!-- 
> PRs which make ingame changes (adding clothing, items, new features,
etc) are required to have media attached that showcase the changes.
> Small fixes/refactors are exempt.
> Any media may be used in SS14 progress reports, with clear credit
given.
> 
> If you're unsure whether your PR will require media, ask a maintainer.
> 
> Check the box below to confirm that you have in fact seen this (put an
X in the brackets, like [X]):
> -->
> Code
> - [X] I have added screenshots/videos to this PR showcasing its
changes ingame, **or** this PR does not require an ingame showcase
> 
> ## Breaking changes
> <!--
> List any breaking changes, including namespace, public
class/method/field changes, prototype renames; and provide instructions
for fixing them. This will be pasted in #codebase-changes.
> -->
> 
> **Changelog**
> <!--
> Make players aware of new features and changes that could affect how
they play the game by adding a Changelog entry. Please read the
Changelog guidelines located at:
https://docs.spacestation14.io/en/getting-started/pr-guideline#changelog
> -->
> 
> <!--
> Make sure to take this Changelog template out of the comment block in
order for it to show up.
> 🆑
> - add: Added fun!
> - remove: Removed fun!
> - tweak: Changed fun!
> - fix: Fixed fun!
> -->
> 


</details>

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: SimpleStation14 <Unknown>
Co-authored-by: VMSolidus <evilexecutive@gmail.com>
2024-05-28 23:36:53 -04:00

325 lines
12 KiB
C#

using System.Diagnostics.CodeAnalysis;
using Content.Client.Popups;
using Content.Shared.Construction;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Construction.Steps;
using Content.Shared.Examine;
using Content.Shared.Input;
using Content.Shared.Interaction;
using Content.Shared.Wall;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Construction
{
/// <summary>
/// The client-side implementation of the construction system, which is used for constructing entities in game.
/// </summary>
[UsedImplicitly]
public sealed class ConstructionSystem : SharedConstructionSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
private readonly Dictionary<int, EntityUid> _ghosts = new();
private readonly Dictionary<string, ConstructionGuide> _guideCache = new();
public bool CraftingEnabled { get; private set; }
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
UpdatesOutsidePrediction = true;
SubscribeLocalEvent<LocalPlayerAttachedEvent>(HandlePlayerAttached);
SubscribeNetworkEvent<AckStructureConstructionMessage>(HandleAckStructure);
SubscribeNetworkEvent<ResponseConstructionGuide>(OnConstructionGuideReceived);
CommandBinds.Builder
.Bind(ContentKeyFunctions.OpenCraftingMenu,
new PointerInputCmdHandler(HandleOpenCraftingMenu, outsidePrediction:true))
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUse, outsidePrediction: true))
.Bind(ContentKeyFunctions.EditorFlipObject,
new PointerInputCmdHandler(HandleFlip, outsidePrediction:true))
.Register<ConstructionSystem>();
SubscribeLocalEvent<ConstructionGhostComponent, ExaminedEvent>(HandleConstructionGhostExamined);
}
private void OnConstructionGuideReceived(ResponseConstructionGuide ev)
{
_guideCache[ev.ConstructionId] = ev.Guide;
ConstructionGuideAvailable?.Invoke(this, ev.ConstructionId);
}
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
CommandBinds.Unregister<ConstructionSystem>();
}
public ConstructionGuide? GetGuide(ConstructionPrototype prototype)
{
if (_guideCache.TryGetValue(prototype.ID, out var guide))
return guide;
RaiseNetworkEvent(new RequestConstructionGuide(prototype.ID));
return null;
}
private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args)
{
if (component.Prototype == null)
return;
using (args.PushGroup(nameof(ConstructionGhostComponent)))
{
args.PushMarkup(Loc.GetString(
"construction-ghost-examine-message",
("name", component.Prototype.Name)));
if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
return;
var startNode = graph.Nodes[component.Prototype.StartNode];
if (!graph.TryPath(component.Prototype.StartNode, component.Prototype.TargetNode, out var path) ||
!startNode.TryGetEdge(path[0].Name, out var edge))
{
return;
}
foreach (var step in edge.Steps)
{
step.DoExamine(args);
}
}
}
public event EventHandler<CraftingAvailabilityChangedArgs>? CraftingAvailabilityChanged;
public event EventHandler<string>? ConstructionGuideAvailable;
public event EventHandler? ToggleCraftingWindow;
public event EventHandler? FlipConstructionPrototype;
private void HandleAckStructure(AckStructureConstructionMessage msg)
{
// We get sent a NetEntity but it actually corresponds to our local Entity.
ClearGhost(msg.GhostId);
}
private void HandlePlayerAttached(LocalPlayerAttachedEvent msg)
{
var available = IsCraftingAvailable(msg.Entity);
UpdateCraftingAvailability(available);
}
private bool HandleOpenCraftingMenu(in PointerInputCmdHandler.PointerInputCmdArgs args)
{
if (args.State == BoundKeyState.Down)
ToggleCraftingWindow?.Invoke(this, EventArgs.Empty);
return true;
}
private bool HandleFlip(in PointerInputCmdHandler.PointerInputCmdArgs args)
{
if (args.State == BoundKeyState.Down)
FlipConstructionPrototype?.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 IsCraftingAvailable(EntityUid? entity)
{
if (entity == default)
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() || !IsClientSide(args.EntityUid))
return false;
if (!HasComp<ConstructionGhostComponent>(args.EntityUid))
return false;
TryStartConstruction(args.EntityUid);
return true;
}
/// <summary>
/// Creates a construction ghost at the given location.
/// </summary>
public void SpawnGhost(ConstructionPrototype prototype, EntityCoordinates loc, Direction dir)
=> TrySpawnGhost(prototype, loc, dir, out _);
/// <summary>
/// Creates a construction ghost at the given location.
/// </summary>
public bool TrySpawnGhost(
ConstructionPrototype prototype,
EntityCoordinates loc,
Direction dir,
[NotNullWhen(true)] out EntityUid? ghost)
{
ghost = null;
if (_playerManager.LocalEntity is not { } user ||
!user.IsValid())
{
return false;
}
if (GhostPresent(loc))
return false;
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
return false;
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))
return false;
ghost = EntityManager.SpawnEntity("constructionghost", loc);
var comp = EntityManager.GetComponent<ConstructionGhostComponent>(ghost.Value);
comp.Prototype = prototype;
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
_ghosts.Add(ghost.GetHashCode(), ghost.Value);
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
sprite.Color = new Color(48, 255, 48, 128);
for (int i = 0; i < prototype.Layers.Count; i++)
{
sprite.AddBlankLayer(i); // There is no way to actually check if this already exists, so we blindly insert a new one
sprite.LayerSetSprite(i, prototype.Layers[i]);
sprite.LayerSetShader(i, "unshaded");
sprite.LayerSetVisible(i, true);
}
if (prototype.CanBuildInImpassable)
EnsureComp<WallMountComponent>(ghost.Value).Arc = new(Math.Tau);
return true;
}
private bool CheckConstructionConditions(ConstructionPrototype prototype, EntityCoordinates loc, Direction dir,
EntityUid user, bool showPopup = false)
{
foreach (var condition in prototype.Conditions)
{
if (!condition.Condition(user, loc, dir))
{
if (showPopup)
{
var message = condition.GenerateGuideEntry()?.Localization;
if (message != null)
{
// Show the reason to the user:
_popupSystem.PopupCoordinates(Loc.GetString(message), loc);
}
}
return false;
}
}
return 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 (EntityManager.GetComponent<TransformComponent>(ghost.Value).Coordinates.Equals(loc))
return true;
}
return false;
}
public void TryStartConstruction(EntityUid ghostId, ConstructionGhostComponent? ghostComp = null)
{
if (!Resolve(ghostId, ref ghostComp))
return;
if (ghostComp.Prototype == null)
{
throw new ArgumentException($"Can't start construction for a ghost with no prototype. Ghost id: {ghostId}");
}
var transform = EntityManager.GetComponent<TransformComponent>(ghostId);
var msg = new TryStartStructureConstructionMessage(GetNetCoordinates(transform.Coordinates), ghostComp.Prototype.ID, transform.LocalRotation, ghostId.GetHashCode());
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))
return;
EntityManager.QueueDeleteEntity(ghost);
_ghosts.Remove(ghostId);
}
/// <summary>
/// Removes all construction ghosts.
/// </summary>
public void ClearAllGhosts()
{
foreach (var ghost in _ghosts.Values)
{
EntityManager.QueueDeleteEntity(ghost);
}
_ghosts.Clear();
}
}
public sealed class CraftingAvailabilityChangedArgs : EventArgs
{
public bool Available { get; }
public CraftingAvailabilityChangedArgs(bool available)
{
Available = available;
}
}
}