using System.Collections.Generic; using Content.Client.Construction; using Content.Client.GameObjects.Components.Construction; using Content.Client.UserInterface; 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.GameObjects.EntitySystems; using Robust.Client.Player; using Robust.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; namespace Content.Client.GameObjects.EntitySystems { /// /// The client-side implementation of the construction system, which is used for constructing entities in game. /// [UsedImplicitly] public class ConstructionSystem : SharedConstructionSystem { [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; private int _nextId; private readonly Dictionary _ghosts = new(); private ConstructionMenu _constructionMenu; /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(HandlePlayerAttached); SubscribeNetworkEvent(HandleAckStructure); CommandBinds.Builder .Bind(ContentKeyFunctions.OpenCraftingMenu, new PointerInputCmdHandler(HandleOpenCraftingMenu)) .Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(HandleUse)) .Register(); } private void HandleAckStructure(AckStructureConstructionMessage msg) { ClearGhost(msg.GhostId); } private void HandlePlayerAttached(PlayerAttachSysMessage msg) { if (msg.AttachedEntity == null) { _gameHud.CraftingButtonVisible = false; return; } if (_constructionMenu == null) { _constructionMenu = new ConstructionMenu(); _constructionMenu.OnClose += () => _gameHud.CraftingButtonDown = false; } _gameHud.CraftingButtonVisible = true; _gameHud.CraftingButtonToggled = b => { if (b) { _constructionMenu.Open(); } else { _constructionMenu.Close(); } }; } /// public override void Shutdown() { _constructionMenu?.Dispose(); CommandBinds.Unregister(); base.Shutdown(); } private bool HandleOpenCraftingMenu(in PointerInputCmdHandler.PointerInputCmdArgs args) { if (_playerManager.LocalPlayer.ControlledEntity == null) { return false; } var menu = _constructionMenu; if (menu.IsOpen) { if (menu.IsAtFront()) { SetOpenValue(menu, false); } else { menu.MoveToFront(); } } else { SetOpenValue(menu, true); } 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(out ConstructionGhostComponent ghostComp)) return false; TryStartConstruction(ghostComp.GhostID); return true; } private void SetOpenValue(ConstructionMenu menu, bool value) { if (value) { _gameHud.CraftingButtonDown = true; menu.OpenCentered(); } else { _gameHud.CraftingButtonDown = false; menu.Close(); } } /// /// Creates a construction ghost at the given location. /// 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(); comp.Prototype = prototype; comp.GhostID = _nextId++; ghost.Transform.LocalRotation = dir.ToAngle(); _ghosts.Add(comp.GhostID, comp); var sprite = ghost.GetComponent(); 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); } /// /// Checks if any construction ghosts are present at the given position /// 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]; var transform = ghost.Owner.Transform; var msg = new TryStartStructureConstructionMessage(transform.Coordinates, ghost.Prototype.ID, transform.LocalRotation, ghostId); RaiseNetworkEvent(msg); } /// /// Starts constructing an item underneath the attached entity. /// public void TryStartItemConstruction(string prototypeName) { RaiseNetworkEvent(new TryStartItemConstructionMessage(prototypeName)); } /// /// Removes a construction ghost entity with the given ID. /// public void ClearGhost(int ghostId) { if (_ghosts.TryGetValue(ghostId, out var ghost)) { ghost.Owner.Delete(); _ghosts.Remove(ghostId); } } /// /// Removes all construction ghosts. /// public void ClearAllGhosts() { foreach (var (_, ghost) in _ghosts) { ghost.Owner.Delete(); } _ghosts.Clear(); } } }