using Barotrauma.Abilities; using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; namespace Barotrauma.Items.Components { class Pickable : ItemComponent, IServerSerializable { protected Character picker; protected List allowedSlots; private float pickTimer; private Character activePicker; private CoroutineHandle pickingCoroutine; public virtual bool IsAttached => false; public List AllowedSlots { get { return allowedSlots; } } public bool PickingDone => pickTimer >= PickingTime; public Character Picker { get { if (picker != null && picker.Removed) { picker = null; } return picker; } } public Pickable(Item item, ContentXElement element) : base(item, element) { allowedSlots = new List(); string slotString = element.GetAttributeString("slots", "Any"); string[] slotCombinations = slotString.Split(','); foreach (string slotCombination in slotCombinations) { string[] slots = slotCombination.Split('+'); InvSlotType allowedSlot = InvSlotType.None; foreach (string slot in slots) { switch (slot.ToLowerInvariant()) { case "bothhands": allowedSlot = InvSlotType.LeftHand | InvSlotType.RightHand; break; default: allowedSlot = allowedSlot | (InvSlotType)Enum.Parse(typeof(InvSlotType), slot.Trim()); break; } } allowedSlots.Add(allowedSlot); } canBePicked = true; } public override bool Pick(Character picker) { //return if someone is already trying to pick the item if (pickTimer > 0.0f) { return false; } if (PickingTime >= float.MaxValue) { return false; } if (picker == null || picker.Inventory == null) { return false; } if (!picker.Inventory.AccessibleWhenAlive && !picker.Inventory.AccessibleByOwner) { return false; } if (PickingTime > 0.0f) { var abilityPickingTime = new AbilityItemPickingTime(PickingTime, item.Prefab); picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime); if (RequiredItems.ContainsKey(RelatedItem.RelationType.Equipped)) { foreach (RelatedItem ri in RequiredItems[RelatedItem.RelationType.Equipped]) { foreach (var heldItem in picker.HeldItems) { if (ri.MatchesItem(heldItem)) { abilityPickingTime.Value /= 1 + heldItem.Prefab.AddedPickingSpeedMultiplier; } } } } if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue) { #if SERVER // Set active picker before creating the server event to make sure it's set correctly activePicker = picker; item.CreateServerEvent(this); #endif pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, abilityPickingTime.Value)); } return false; } else { return OnPicked(picker); } } public virtual bool OnPicked(Character picker) { return OnPicked(picker, pickDroppedStack: true); } public virtual bool OnPicked(Character picker, bool pickDroppedStack) { //if the item has multiple Pickable components (e.g. Holdable and Wearable, check that we don't equip it in hands when the item is worn or vice versa) if (item.GetComponents().Any()) { bool alreadyEquipped = false; for (int i = 0; i < picker.Inventory.Capacity; i++) { if (picker.Inventory.GetItemsAt(i).Contains(item)) { if (picker.Inventory.SlotTypes[i] != InvSlotType.Any && !allowedSlots.Any(a => a.HasFlag(picker.Inventory.SlotTypes[i]))) { alreadyEquipped = true; break; } } } if (alreadyEquipped) { return false; } } var droppedStack = pickDroppedStack ? item.DroppedStack.ToList() : null; if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots)) { if (!picker.HeldItems.Contains(item) && item.body != null) { item.body.Enabled = false; } this.picker = picker; for (int i = item.linkedTo.Count - 1; i >= 0; i--) { item.linkedTo[i].RemoveLinked(item); } item.linkedTo.Clear(); DropConnectedWires(picker); ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker); #if CLIENT if (!GameMain.Instance.LoadingScreenOpen && picker == Character.Controlled) { SoundPlayer.PlayUISound(GUISoundType.PickItem); } PlaySound(ActionType.OnPicked, picker); #endif if (pickDroppedStack) { foreach (var droppedItem in droppedStack) { if (droppedItem == item) { continue; } droppedItem.GetComponent().OnPicked(picker, pickDroppedStack: false); } } return true; } #if CLIENT if (!GameMain.Instance.LoadingScreenOpen && picker == Character.Controlled) { SoundPlayer.PlayUISound(GUISoundType.PickItemFail); } #endif return false; } private IEnumerable WaitForPick(Character picker, float requiredTime) { activePicker = picker; picker.PickingItem = item; pickTimer = 0.0f; while (pickTimer < requiredTime && Screen.Selected != GameMain.SubEditorScreen) { if (!CoroutineManager.Paused) { //cancel if the item is currently selected //attempting to pick does not select the item, so if it is selected at this point, another ItemComponent //must have been selected and we should not keep deattaching (happens when for example interacting with //an electrical component while holding both a screwdriver and a wrench). if (picker.IsAnySelectedItem(item) || picker.IsKeyDown(InputType.Aim) || !picker.CanInteractWith(item) || item.Removed || item.ParentInventory != null) { StopPicking(picker); yield return CoroutineStatus.Success; } #if CLIENT if (requiredTime < float.MaxValue && picker == Character.Controlled) { string text = string.Empty; if (!string.IsNullOrWhiteSpace(PickingMsg)) { text = PickingMsg; } else if (this is Door door) { text = door.IsClosed ? "progressbar.opening" : "progressbar.closing"; } else { text = "progressbar.deattaching"; } Character.Controlled?.UpdateHUDProgressBar( this, item.WorldPosition, pickTimer / requiredTime, GUIStyle.Red, GUIStyle.Green, text); } #endif picker.AnimController.UpdateUseItem(!picker.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f)); pickTimer += CoroutineManager.DeltaTime; } yield return CoroutineStatus.Running; } StopPicking(picker); if (!item.Removed) { bool isNotRemote = true; #if CLIENT isNotRemote = !picker.IsRemotePlayer; #endif if (isNotRemote) { OnPicked(picker); } } yield return CoroutineStatus.Success; } protected void StopPicking(Character picker) { if (picker != null) { picker.AnimController.StopUsingItem(); picker.PickingItem = null; } if (pickingCoroutine != null) { CoroutineManager.StopCoroutines(pickingCoroutine); pickingCoroutine = null; } activePicker = null; pickTimer = 0.0f; } protected void DropConnectedWires(Character character) { Vector2 pos = character == null ? item.SimPosition : character.SimPosition; foreach (ConnectionPanel connectionPanel in item.GetComponents()) { connectionPanel.DisconnectedWires.Clear(); foreach (Connection c in connectionPanel.Connections) { foreach (Wire w in c.Wires.ToArray()) { if (w == null) continue; w.Item.Drop(character); w.Item.SetTransform(pos, 0.0f); } } } } public override void Drop(Character dropper, bool setTransform = true) { picker ??= dropper; Vector2 bodyDropPos = Vector2.Zero; if (picker == null || picker.Inventory == null) { if (item.ParentInventory != null && item.ParentInventory.Owner != null && !item.ParentInventory.Owner.Removed) { bodyDropPos = item.ParentInventory.Owner.SimPosition; item.body?.ResetDynamics(); } } else if (!picker.Removed) { DropConnectedWires(picker); item.Submarine = picker.Submarine; bodyDropPos = picker.SimPosition; picker.Inventory.RemoveItem(item); picker = null; } if (item.body != null && !item.body.Enabled && setTransform) { if (item.body.Removed) { DebugConsole.ThrowError( "Failed to drop the Pickable component of the item \"" + item.Name + "\" (body has been removed" + (item.Removed ? ", item has been removed)" : ")")); } else { item.body.ResetDynamics(); item.SetTransform(bodyDropPos, 0.0f); item.body.Enabled = true; } } } public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { msg.WriteUInt16(activePicker?.ID ?? (ushort)0); } public virtual void ClientEventRead(IReadMessage msg, float sendingTime) { ushort pickerID = msg.ReadUInt16(); if (pickerID == 0) { StopPicking(activePicker); } else { Pick(Entity.FindEntityByID(pickerID) as Character); } } } class AbilityItemPickingTime : AbilityObject, IAbilityValue, IAbilityItemPrefab { public AbilityItemPickingTime(float pickingTime, ItemPrefab itemPrefab) { Value = pickingTime; ItemPrefab = itemPrefab; } public float Value { get; set; } public ItemPrefab ItemPrefab { get; set; } } }