<commit_before>﻿using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.Components.Construction;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.Construction;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Interactable;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;

namespace Content.Server.GameObjects.EntitySystems
{
    /// <summary>
    /// The server-side implementation of the construction system, which is used for constructing entities in game.
    /// </summary>
    [UsedImplicitly]
    internal class ConstructionSystem : Shared.GameObjects.EntitySystems.ConstructionSystem
    {
#pragma warning disable 649
        [Dependency] private readonly IPrototypeManager _prototypeManager;
        [Dependency] private readonly IMapManager _mapManager;
        [Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649

        private readonly Dictionary<string, ConstructionPrototype> _craftRecipes = new Dictionary<string, ConstructionPrototype>();

        /// <inheritdoc />
        public override void Initialize()
        {
            base.Initialize();

            foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
            {
                _craftRecipes.Add(prototype.Result, prototype);
            }

            SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
            SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);

            SubscribeLocalEvent<AfterInteractMessage>(HandleToolInteraction);
        }

        private void HandleStartStructureConstruction(TryStartStructureConstructionMessage msg, EntitySessionEventArgs args)
        {
            var placingEnt = args.SenderSession.AttachedEntity;
            var result = TryStartStructureConstruction(placingEnt, msg.Location, msg.PrototypeName, msg.Angle);
            if (!result) return;
            var responseMsg = new AckStructureConstructionMessage(msg.Ack);
            var channel = ((IPlayerSession) args.SenderSession).ConnectedClient;
            RaiseNetworkEvent(responseMsg, channel);
        }

        private void HandleStartItemConstruction(TryStartItemConstructionMessage msg, EntitySessionEventArgs args)
        {
            var placingEnt = args.SenderSession.AttachedEntity;
            TryStartItemConstruction(placingEnt, msg.PrototypeName);
        }

        private void HandleToolInteraction(AfterInteractMessage msg)
        {
            if(msg.Handled)
                return;

            // You can only construct/deconstruct things within reach
            if(!msg.CanReach)
                return;

            var targetEnt = msg.Attacked;
            var handEnt = msg.ItemInHand;

            // A tool has to interact with an entity.
            if(targetEnt is null || handEnt is null)
                return;

            var interaction = Get<InteractionSystem>();
            if(!interaction.InRangeUnobstructed(handEnt.Transform.MapPosition, targetEnt.Transform.MapPosition, ignoredEnt: targetEnt, ignoreInsideBlocker: true))
                return;

            // Cannot deconstruct an entity with no prototype.
            var targetPrototype = targetEnt.MetaData.EntityPrototype;
            if (targetPrototype is null)
                return;

            // the target entity is in the process of being constructed/deconstructed
            if (msg.Attacked.TryGetComponent<ConstructionComponent>(out var constructComp))
            {
                var result = TryConstructEntity(constructComp, handEnt, msg.User);

                // TryConstructEntity may delete the existing entity

                msg.Handled = result;
            }
            else // try to start the deconstruction process
            {
                // A tool was not used on the entity.
                if (!handEnt.TryGetComponent<IToolComponent>(out var toolComp))
                    return;

                // no known recipe for entity
                if (!_craftRecipes.TryGetValue(targetPrototype.ID, out var prototype))
                {
                    _notifyManager.PopupMessage(msg.Attacked, msg.User,
                        "Cannot be deconstructed.");
                    return;
                }

                // there is a recipe, but it can't be deconstructed.
                var lastStep = prototype.Stages[^1].Backward;
                if (!(lastStep is ConstructionStepTool))
                {
                    _notifyManager.PopupMessage(msg.Attacked, msg.User,
                        "Cannot be deconstructed.");
                    return;
                }

                // wrong tool
                var caps = ((ConstructionStepTool) lastStep).ToolQuality;
                if ((toolComp.Qualities & caps) == 0)
                {
                    _notifyManager.PopupMessage(msg.Attacked, msg.User,
                        "Wrong tool to start deconstruct.");
                    return;
                }

                // ask around and see if the deconstruction prerequisites are satisfied
                // (remove bulbs, approved access, open panels, etc)
                var deconCompMsg = new BeginDeconstructCompMsg(msg.User);
                targetEnt.SendMessage(null, deconCompMsg);
                if(deconCompMsg.BlockDeconstruct)
                    return;

                var deconEntMsg = new BeginDeconstructEntityMsg(msg.User, handEnt, targetEnt);
                RaiseLocalEvent(deconEntMsg);
                if(deconEntMsg.BlockDeconstruct)
                    return;

                // --- GOOD TO GO ---

                // pop off the material and switch to frame
                var targetEntPos = targetEnt.Transform.MapPosition;
                if (prototype.Stages.Count <= 2) // there are no intermediate stages
                {
                    targetEnt.Delete();

                    SpawnIngredient(targetEntPos, prototype.Stages[(prototype.Stages.Count - 2)].Forward as ConstructionStepMaterial);
                }
                else // replace ent with intermediate
                {
                    // Spawn frame
                    var frame = SpawnCopyTransform("structureconstructionframe", targetEnt.Transform);
                    var construction = frame.GetComponent<ConstructionComponent>();
                    SetupComponent(construction, prototype);
                    construction.Stage = prototype.Stages.Count - 2;
                    SetupDeconIntermediateSprite(construction, prototype);
                    frame.Transform.LocalRotation = targetEnt.Transform.LocalRotation;

                    if (targetEnt.Prototype.Components.TryGetValue("Item", out var itemProtoComp))
                    {
                        if(frame.HasComponent<ItemComponent>())
                            frame.RemoveComponent<ItemComponent>();

                        var itemComp = frame.AddComponent<ItemComponent>();

                        var serializer = YamlObjectSerializer.NewReader(itemProtoComp);
                        itemComp.ExposeData(serializer);
                    }

                    ReplaceInContainerOrGround(targetEnt, frame);

                    // remove target
                    targetEnt.Delete();

                    // spawn material
                    SpawnIngredient(targetEntPos, prototype.Stages[(prototype.Stages.Count-2)].Forward as ConstructionStepMaterial);
                }
            }
        }

        private IEntity SpawnCopyTransform(string prototypeId, ITransformComponent toReplace)
        {
            var frame = EntityManager.SpawnEntity(prototypeId, toReplace.MapPosition);
            frame.Transform.WorldRotation = toReplace.WorldRotation;
            frame.Transform.ParentUid = toReplace.ParentUid;
            return frame;
        }

        private static void SetupDeconIntermediateSprite(ConstructionComponent constructionComponent, ConstructionPrototype prototype)
        {
            if(!constructionComponent.Owner.TryGetComponent<SpriteComponent>(out var spriteComp))
                return;

            for (var i = prototype.Stages.Count - 1; i >= 0; i--)
            {
                if (prototype.Stages[i].Icon != null)
                {
                    spriteComp.AddLayerWithSprite(prototype.Stages[1].Icon);
                    return;
                }
            }

            spriteComp.AddLayerWithSprite(prototype.Icon);
        }

        private void SpawnIngredient(MapCoordinates position, ConstructionStepMaterial lastStep)
        {
            if(lastStep is null)
                return;

            var material = lastStep.Material;
            var quantity = lastStep.Amount;

            var matEnt = EntityManager.SpawnEntity(MaterialPrototypes[material], position);
            if (matEnt.TryGetComponent<StackComponent>(out var stackComp))
            {
                stackComp.Count = quantity;
            }
            else
            {
                quantity--; // already spawned one above
                while (quantity > 0)
                {
                    EntityManager.SpawnEntity(MaterialPrototypes[material], position);
                    quantity--;
                }
            }
        }

        private static readonly Dictionary<ConstructionStepMaterial.MaterialType, string> MaterialPrototypes =
            new Dictionary<ConstructionStepMaterial.MaterialType, string>
            {
                { ConstructionStepMaterial.MaterialType.Cable, "CableStack1" },
                { ConstructionStepMaterial.MaterialType.Gold, "GoldStack1" },
                { ConstructionStepMaterial.MaterialType.Metal, "SteelSheet1" },
                { ConstructionStepMaterial.MaterialType.Glass, "GlassSheet1" }
            };

        private bool TryStartStructureConstruction(IEntity placingEnt, GridCoordinates loc, string prototypeName, Angle angle)
        {
            var prototype = _prototypeManager.Index<ConstructionPrototype>(prototypeName);

            if (!InteractionChecks.InRangeUnobstructed(placingEnt, loc.ToMap(_mapManager),
                ignoredEnt: placingEnt, ignoreInsideBlocker: prototype.CanBuildInImpassable))
            {
                return false;
            }

            if (prototype.Stages.Count < 2)
            {
                throw new InvalidOperationException($"Prototype '{prototypeName}' does not have enough stages.");
            }

            var stage0 = prototype.Stages[0];
            if (!(stage0.Forward is ConstructionStepMaterial matStep))
            {
                throw new NotImplementedException();
            }

            // Try to find the stack with the material in the user's hand.
            var hands = placingEnt.GetComponent<HandsComponent>();
            var activeHand = hands.GetActiveHand?.Owner;<commit_msg>Fix construction ghost HandsComponent check (#1386)<commit_after>﻿using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.Components.Construction;
using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.Construction;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Interactable;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;

namespace Content.Server.GameObjects.EntitySystems
{
    /// <summary>
    /// The server-side implementation of the construction system, which is used for constructing entities in game.
    /// </summary>
    [UsedImplicitly]
    internal class ConstructionSystem : Shared.GameObjects.EntitySystems.ConstructionSystem
    {
#pragma warning disable 649
        [Dependency] private readonly IPrototypeManager _prototypeManager;
        [Dependency] private readonly IMapManager _mapManager;
        [Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649

        private readonly Dictionary<string, ConstructionPrototype> _craftRecipes = new Dictionary<string, ConstructionPrototype>();

        /// <inheritdoc />
        public override void Initialize()
        {
            base.Initialize();

            foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
            {
                _craftRecipes.Add(prototype.Result, prototype);
            }

            SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
            SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);

            SubscribeLocalEvent<AfterInteractMessage>(HandleToolInteraction);
        }

        private void HandleStartStructureConstruction(TryStartStructureConstructionMessage msg, EntitySessionEventArgs args)
        {
            var placingEnt = args.SenderSession.AttachedEntity;
            var result = TryStartStructureConstruction(placingEnt, msg.Location, msg.PrototypeName, msg.Angle);
            if (!result) return;
            var responseMsg = new AckStructureConstructionMessage(msg.Ack);
            var channel = ((IPlayerSession) args.SenderSession).ConnectedClient;
            RaiseNetworkEvent(responseMsg, channel);
        }

        private void HandleStartItemConstruction(TryStartItemConstructionMessage msg, EntitySessionEventArgs args)
        {
            var placingEnt = args.SenderSession.AttachedEntity;
            TryStartItemConstruction(placingEnt, msg.PrototypeName);
        }

        private void HandleToolInteraction(AfterInteractMessage msg)
        {
            if(msg.Handled)
                return;

            // You can only construct/deconstruct things within reach
            if(!msg.CanReach)
                return;

            var targetEnt = msg.Attacked;
            var handEnt = msg.ItemInHand;

            // A tool has to interact with an entity.
            if(targetEnt is null || handEnt is null)
                return;

            var interaction = Get<InteractionSystem>();
            if(!interaction.InRangeUnobstructed(handEnt.Transform.MapPosition, targetEnt.Transform.MapPosition, ignoredEnt: targetEnt, ignoreInsideBlocker: true))
                return;

            // Cannot deconstruct an entity with no prototype.
            var targetPrototype = targetEnt.MetaData.EntityPrototype;
            if (targetPrototype is null)
                return;

            // the target entity is in the process of being constructed/deconstructed
            if (msg.Attacked.TryGetComponent<ConstructionComponent>(out var constructComp))
            {
                var result = TryConstructEntity(constructComp, handEnt, msg.User);

                // TryConstructEntity may delete the existing entity

                msg.Handled = result;
            }
            else // try to start the deconstruction process
            {
                // A tool was not used on the entity.
                if (!handEnt.TryGetComponent<IToolComponent>(out var toolComp))
                    return;

                // no known recipe for entity
                if (!_craftRecipes.TryGetValue(targetPrototype.ID, out var prototype))
                {
                    _notifyManager.PopupMessage(msg.Attacked, msg.User,
                        "Cannot be deconstructed.");
                    return;
                }

                // there is a recipe, but it can't be deconstructed.
                var lastStep = prototype.Stages[^1].Backward;
                if (!(lastStep is ConstructionStepTool))
                {
                    _notifyManager.PopupMessage(msg.Attacked, msg.User,
                        "Cannot be deconstructed.");
                    return;
                }

                // wrong tool
                var caps = ((ConstructionStepTool) lastStep).ToolQuality;
                if ((toolComp.Qualities & caps) == 0)
                {
                    _notifyManager.PopupMessage(msg.Attacked, msg.User,
                        "Wrong tool to start deconstruct.");
                    return;
                }

                // ask around and see if the deconstruction prerequisites are satisfied
                // (remove bulbs, approved access, open panels, etc)
                var deconCompMsg = new BeginDeconstructCompMsg(msg.User);
                targetEnt.SendMessage(null, deconCompMsg);
                if(deconCompMsg.BlockDeconstruct)
                    return;

                var deconEntMsg = new BeginDeconstructEntityMsg(msg.User, handEnt, targetEnt);
                RaiseLocalEvent(deconEntMsg);
                if(deconEntMsg.BlockDeconstruct)
                    return;

                // --- GOOD TO GO ---

                // pop off the material and switch to frame
                var targetEntPos = targetEnt.Transform.MapPosition;
                if (prototype.Stages.Count <= 2) // there are no intermediate stages
                {
                    targetEnt.Delete();

                    SpawnIngredient(targetEntPos, prototype.Stages[(prototype.Stages.Count - 2)].Forward as ConstructionStepMaterial);
                }
                else // replace ent with intermediate
                {
                    // Spawn frame
                    var frame = SpawnCopyTransform("structureconstructionframe", targetEnt.Transform);
                    var construction = frame.GetComponent<ConstructionComponent>();
                    SetupComponent(construction, prototype);
                    construction.Stage = prototype.Stages.Count - 2;
                    SetupDeconIntermediateSprite(construction, prototype);
                    frame.Transform.LocalRotation = targetEnt.Transform.LocalRotation;

                    if (targetEnt.Prototype.Components.TryGetValue("Item", out var itemProtoComp))
                    {
                        if(frame.HasComponent<ItemComponent>())
                            frame.RemoveComponent<ItemComponent>();

                        var itemComp = frame.AddComponent<ItemComponent>();

                        var serializer = YamlObjectSerializer.NewReader(itemProtoComp);
                        itemComp.ExposeData(serializer);
                    }

                    ReplaceInContainerOrGround(targetEnt, frame);

                    // remove target
                    targetEnt.Delete();

                    // spawn material
                    SpawnIngredient(targetEntPos, prototype.Stages[(prototype.Stages.Count-2)].Forward as ConstructionStepMaterial);
                }
            }
        }

        private IEntity SpawnCopyTransform(string prototypeId, ITransformComponent toReplace)
        {
            var frame = EntityManager.SpawnEntity(prototypeId, toReplace.MapPosition);
            frame.Transform.WorldRotation = toReplace.WorldRotation;
            frame.Transform.ParentUid = toReplace.ParentUid;
            return frame;
        }

        private static void SetupDeconIntermediateSprite(ConstructionComponent constructionComponent, ConstructionPrototype prototype)
        {
            if(!constructionComponent.Owner.TryGetComponent<SpriteComponent>(out var spriteComp))
                return;

            for (var i = prototype.Stages.Count - 1; i >= 0; i--)
            {
                if (prototype.Stages[i].Icon != null)
                {
                    spriteComp.AddLayerWithSprite(prototype.Stages[1].Icon);
                    return;
                }
            }

            spriteComp.AddLayerWithSprite(prototype.Icon);
        }

        private void SpawnIngredient(MapCoordinates position, ConstructionStepMaterial lastStep)
        {
            if(lastStep is null)
                return;

            var material = lastStep.Material;
            var quantity = lastStep.Amount;

            var matEnt = EntityManager.SpawnEntity(MaterialPrototypes[material], position);
            if (matEnt.TryGetComponent<StackComponent>(out var stackComp))
            {
                stackComp.Count = quantity;
            }
            else
            {
                quantity--; // already spawned one above
                while (quantity > 0)
                {
                    EntityManager.SpawnEntity(MaterialPrototypes[material], position);
                    quantity--;
                }
            }
        }

        private static readonly Dictionary<ConstructionStepMaterial.MaterialType, string> MaterialPrototypes =
            new Dictionary<ConstructionStepMaterial.MaterialType, string>
            {
                { ConstructionStepMaterial.MaterialType.Cable, "CableStack1" },
                { ConstructionStepMaterial.MaterialType.Gold, "GoldStack1" },
                { ConstructionStepMaterial.MaterialType.Metal, "SteelSheet1" },
                { ConstructionStepMaterial.MaterialType.Glass, "GlassSheet1" }
            };

        private bool TryStartStructureConstruction(IEntity placingEnt, GridCoordinates loc, string prototypeName, Angle angle)
        {
            var prototype = _prototypeManager.Index<ConstructionPrototype>(prototypeName);

            if (!InteractionChecks.InRangeUnobstructed(placingEnt, loc.ToMap(_mapManager),
                ignoredEnt: placingEnt, ignoreInsideBlocker: prototype.CanBuildInImpassable))
            {
                return false;
            }

            if (prototype.Stages.Count < 2)
            {
                throw new InvalidOperationException($"Prototype '{prototypeName}' does not have enough stages.");
            }

            var stage0 = prototype.Stages[0];
            if (!(stage0.Forward is ConstructionStepMaterial matStep))
            {
                throw new NotImplementedException();
            }

            // Try to find the stack with the material in the user's hand.
            if(!placingEnt.TryGetComponent<HandsComponent>(out var hands))
            {
                return false;
            };
            var activeHand = hands.GetActiveHand?.Owner;
{'fraction_non_alphanumeric': 0.07494601346487699, 'fraction_numerical': 0.0022018037854088157, 'mean_word_length': 1.9930300342161957, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2890887', 'n_tokens_mistral': 5613, 'n_tokens_neox': 5262, 'n_words': 1206}