diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index 6fad7de4a6..8dbc915a17 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -62,6 +62,9 @@ + + + @@ -80,6 +83,10 @@ {31d24303-f6a9-4d53-bb03-a73edcb3186d} sfml-system + + {d17de83d-a592-461f-8af2-53f9e22e1d0f} + sfml-window + {302b877e-0000-0000-0000-000000000000} SS14.Client.Graphics @@ -103,4 +110,4 @@ - + \ No newline at end of file diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 23c31b9e9b..cd7fc67701 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -1,4 +1,8 @@ -using SS14.Shared.ContentPack; +using Content.Client.GameObjects; +using Content.Client.Interfaces.GameObjects; +using SS14.Shared.ContentPack; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; namespace Content.Client { @@ -6,7 +10,13 @@ namespace Content.Client { public override void Init() { - // TODO: Anything at all. + var factory = IoCManager.Resolve(); + + factory.RegisterIgnore("Inventory"); + factory.RegisterIgnore("Item"); + + factory.Register(); + factory.RegisterReference(); } } } diff --git a/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs b/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs new file mode 100644 index 0000000000..3688198afe --- /dev/null +++ b/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs @@ -0,0 +1,55 @@ +using Content.Client.Interfaces.GameObjects; +using Content.Client.UserInterface; +using Content.Shared.GameObjects; +using Lidgren.Network; +using SS14.Client.Interfaces.UserInterface; +using SS14.Client.UserInterface; +using SS14.Shared; +using SS14.Shared.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using System.Collections.Generic; + +namespace Content.Client.GameObjects +{ + public class HandsComponent : SharedHandsComponent, IHandsComponent + { + private readonly Dictionary hands = new Dictionary(); + public string ActiveIndex { get; private set; } + + public IEntity GetEntity(string index) + { + if (hands.TryGetValue(index, out var entity)) + { + return entity; + } + + return null; + } + + public override void HandleComponentState(ComponentState state) + { + var cast = (HandsComponentState)state; + hands.Clear(); + foreach (var hand in cast.Hands) + { + hands[hand.Key] = Owner.EntityManager.GetEntity(hand.Value); + } + + ActiveIndex = cast.ActiveIndex; + + var uiMgr = (UserInterfaceManager)IoCManager.Resolve(); + + if (uiMgr.GetSingleComponentByGuiComponentType(GuiComponentType.HandsUi) == null) + { + uiMgr.AddComponent(new HandsGui()); + } + uiMgr.ComponentUpdate(GuiComponentType.HandsUi, this); + } + + public void SendChangeHand(string index) + { + Owner.SendComponentNetworkMessage(this, NetDeliveryMethod.ReliableUnordered, index); + } + } +} diff --git a/Content.Client/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Client/Interfaces/GameObjects/Components/Items/IHandsComponent.cs new file mode 100644 index 0000000000..91002b3cc9 --- /dev/null +++ b/Content.Client/Interfaces/GameObjects/Components/Items/IHandsComponent.cs @@ -0,0 +1,14 @@ +using SS14.Shared.Interfaces.GameObjects; + +namespace Content.Client.Interfaces.GameObjects +{ + // HYPER SIMPLE HANDS API CLIENT SIDE. + // To allow for showing the HUD, mostly. + public interface IHandsComponent + { + IEntity GetEntity(string index); + string ActiveIndex { get; } + + void SendChangeHand(string index); + } +} diff --git a/Content.Client/UserInterface/HandsGui.cs b/Content.Client/UserInterface/HandsGui.cs new file mode 100644 index 0000000000..92eb8be029 --- /dev/null +++ b/Content.Client/UserInterface/HandsGui.cs @@ -0,0 +1,192 @@ +using Content.Client.Interfaces.GameObjects; +using OpenTK.Graphics; +using SFML.Graphics; +using SFML.Window; +using SS14.Client.GameObjects; +using SS14.Client.Graphics; +using SS14.Client.Graphics.Utility; +using SS14.Client.Interfaces.Player; +using SS14.Client.Interfaces.Resource; +using SS14.Client.Interfaces.UserInterface; +using SS14.Client.UserInterface.Components; +using SS14.Shared; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; +using SS14.Shared.Maths; + +namespace Content.Client.UserInterface +{ + public class HandsGui : GuiComponent + { + private readonly Color4 _inactiveColor = new Color4(90, 90, 90, 255); + + private readonly IPlayerManager _playerManager = IoCManager.Resolve(); + private readonly IUserInterfaceManager _userInterfaceManager = IoCManager.Resolve(); + private readonly Sprite handSlot; + private readonly int spacing = 1; + + private UiHandInfo LeftHand; + private UiHandInfo RightHand; + private Box2i handL; + private Box2i handR; + + public HandsGui() + { + var _resMgr = IoCManager.Resolve(); + ComponentClass = GuiComponentType.HandsUi; + handSlot = _resMgr.GetSprite("hand"); + ZDepth = 5; + } + + public override void ComponentUpdate(params object[] args) + { + base.ComponentUpdate(args); + UpdateHandIcons(); + } + + public override void Update(float frameTime) + { + var slotBounds = handSlot.GetLocalBounds(); + var width = (int)((slotBounds.Width * 2) + spacing); + var height = (int)slotBounds.Height; + Position = new Vector2i((int)(CluwneLib.Window.Viewport.Width - width) / 2, (int)CluwneLib.Window.Viewport.Height - height - 10); + handL = Box2i.FromDimensions(Position.X, Position.Y, (int)slotBounds.Width, (int)slotBounds.Height); + handR = Box2i.FromDimensions(Position.X + (int)slotBounds.Width + spacing, Position.Y, (int)slotBounds.Width, (int)slotBounds.Height); + ClientArea = Box2i.FromDimensions(Position.X, Position.Y, width, (int)slotBounds.Height); + } + + public override void Render() + { + if (_playerManager?.ControlledEntity == null) + { + return; + } + + IEntity entity = _playerManager.ControlledEntity; + if (!entity.TryGetComponent(out var hands)) + { + return; + } + + var leftActive = hands.ActiveIndex == "left"; + + handSlot.Color = Color.White; + handSlot.SetTransformToRect(leftActive ? handL : handR); + handSlot.Draw(); + + handSlot.Color = _inactiveColor.Convert(); + handSlot.SetTransformToRect(leftActive ? handR : handL); + handSlot.Draw(); + + if (LeftHand.Entity != null && LeftHand.HeldSprite != null) + { + var bounds = LeftHand.HeldSprite.GetLocalBounds(); + LeftHand.HeldSprite.SetTransformToRect( + Box2i.FromDimensions(handL.Left + (int)(handL.Width / 2f - bounds.Width / 2f), + handL.Top + (int)(handL.Height / 2f - bounds.Height / 2f), + (int)bounds.Width, (int)bounds.Height)); + LeftHand.HeldSprite.Draw(); + } + + if (RightHand.Entity != null && RightHand.HeldSprite != null) + { + var bounds = RightHand.HeldSprite.GetLocalBounds(); + RightHand.HeldSprite.SetTransformToRect( + Box2i.FromDimensions(handR.Left + (int)(handR.Width / 2f - bounds.Width / 2f), + handR.Top + (int)(handR.Height / 2f - bounds.Height / 2f), + (int)bounds.Width, (int)bounds.Height)); + RightHand.HeldSprite.Draw(); + } + } + + public void UpdateHandIcons() + { + if (_playerManager?.ControlledEntity == null) + { + return; + } + + IEntity entity = _playerManager.ControlledEntity; + if (!entity.TryGetComponent(out var hands)) + { + return; + } + + var left = hands.GetEntity("left"); + var right = hands.GetEntity("right"); + + if (left != null) + { + if (left != LeftHand.Entity) + { + LeftHand.Entity = left; + LeftHand.HeldSprite = GetIconSprite(left); + } + } + else + { + LeftHand.Entity = null; + LeftHand.HeldSprite = null; + } + + if (right != null) + { + if (right != RightHand.Entity) + { + RightHand.Entity = right; + RightHand.HeldSprite = GetIconSprite(right); + } + } + else + { + RightHand.Entity = null; + RightHand.HeldSprite = null; + } + } + + private void SendSwitchHandTo(string index) + { + IEntity entity = _playerManager.ControlledEntity; + if (!entity.TryGetComponent(out var hands)) + { + return; + } + hands.SendChangeHand(index); + } + + public override bool MouseDown(MouseButtonEventArgs e) + { + if (e.Button != Mouse.Button.Right) + { + return false; + } + if (handL.Contains(e.X, e.Y)) + { + SendSwitchHandTo("left"); + return true; + } + if (handR.Contains(e.X, e.Y)) + { + SendSwitchHandTo("right"); + return true; + } + return false; + } + + private static Sprite GetIconSprite(IEntity entity) + { + Sprite icon = null; + if (entity.TryGetComponent(out var component)) + { + icon = component.Icon; + } + return icon ?? IoCManager.Resolve().DefaultSprite(); + } + + private struct UiHandInfo + { + public IEntity Entity { get; set; } + public Sprite HeldSprite { get; set; } + } + } +} diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index 07b85bcc33..da2735d7f1 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -56,6 +56,12 @@ + + + + + + @@ -85,4 +91,4 @@ - + \ No newline at end of file diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 6483e6b6f4..c8c527c982 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -1,4 +1,8 @@ -using SS14.Shared.ContentPack; +using Content.Server.GameObjects; +using Content.Server.Interfaces.GameObjects; +using SS14.Shared.ContentPack; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.IoC; namespace Content.Server { @@ -6,7 +10,16 @@ namespace Content.Server { public override void Init() { - // TODO: Anything at all. + var factory = IoCManager.Resolve(); + + factory.Register(); + factory.RegisterReference(); + + factory.Register(); + factory.RegisterReference(); + + factory.Register(); + factory.RegisterReference(); } } } diff --git a/Content.Server/GameObjects/Components/Items/InventoryComponent.cs b/Content.Server/GameObjects/Components/Items/InventoryComponent.cs new file mode 100644 index 0000000000..585235d948 --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/InventoryComponent.cs @@ -0,0 +1,167 @@ +using Content.Server.Interfaces.GameObjects; +using SS14.Server.GameObjects; +using SS14.Server.GameObjects.Components.Container; +using SS14.Server.Interfaces.GameObjects; +using SS14.Shared.Utility; +using SS14.Shared.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using System; +using System.Collections.Generic; +using YamlDotNet.RepresentationModel; + +namespace Content.Server.GameObjects +{ + public class InventoryComponent : Component, IInventoryComponent + { + public override string Name => "Inventory"; + + private Dictionary slots = new Dictionary(); + private TransformComponent transform; + // TODO: Make this container unique per-slot. + private IContainer container; + + public override void Initialize() + { + transform = Owner.GetComponent(); + container = Container.Create("inventory", Owner); + base.Initialize(); + } + + public override void OnRemove() + { + foreach (var slot in slots.Keys) + { + RemoveSlot(slot); + } + transform = null; + container = null; + base.OnRemove(); + } + + public override void LoadParameters(YamlMappingNode mapping) + { + if (mapping.TryGetNode("slots", out var slotsNode)) + { + foreach (var node in slotsNode) + { + AddSlot(node.AsString()); + } + } + base.LoadParameters(mapping); + } + + public IItemComponent Get(string slot) + { + return _GetSlot(slot).Item; + } + + public IInventorySlot GetSlot(string slot) + { + return slots[slot]; + } + + // Private version that returns our concrete implementation. + private InventorySlot _GetSlot(string slot) + { + return slots[slot]; + } + + public bool Insert(string slot, IItemComponent item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item), "An item must be passed. To remove an item from a slot, use Drop()"); + } + + var inventorySlot = _GetSlot(slot); + if (!CanInsert(slot, item) || !container.Insert(item.Owner)) + { + return false; + } + + inventorySlot.Item = item; + item.EquippedToSlot(inventorySlot); + return true; + } + + public bool CanInsert(string slot, IItemComponent item) + { + var inventorySlot = _GetSlot(slot); + return inventorySlot.Item == null && container.CanInsert(item.Owner); + } + + public bool Drop(string slot) + { + if (!CanDrop(slot)) + { + return false; + } + + var inventorySlot = _GetSlot(slot); + var item = inventorySlot.Item; + if (!container.Remove(item.Owner)) + { + return false; + } + + item.RemovedFromSlot(); + inventorySlot.Item = null; + + // TODO: The item should be dropped to the container our owner is in, if any. + var itemTransform = item.Owner.GetComponent(); + itemTransform.LocalPosition = transform.LocalPosition; + return true; + } + + public bool CanDrop(string slot) + { + var inventorySlot = _GetSlot(slot); + var item = inventorySlot.Item; + return item != null && container.CanRemove(item.Owner); + } + + public IInventorySlot AddSlot(string slot) + { + if (HasSlot(slot)) + { + throw new InvalidOperationException($"Slot '{slot}' already exists."); + } + + return slots[slot] = new InventorySlot(slot, this); + } + + public void RemoveSlot(string slot) + { + if (!HasSlot(slot)) + { + throw new InvalidOperationException($"Slow '{slot}' does not exist."); + } + + if (Get(slot) != null && !Drop(slot)) + { + // TODO: Handle this potential failiure better. + throw new InvalidOperationException("Unable to remove slot as the contained item could not be dropped"); + } + + slots.Remove(slot); + } + + public bool HasSlot(string slot) + { + return slots.ContainsKey(slot); + } + + private class InventorySlot : IInventorySlot + { + public IItemComponent Item { get; set; } + public string Name { get; } + public IInventoryComponent Owner { get; } + + public InventorySlot(string name, IInventoryComponent owner) + { + Name = name; + Owner = owner; + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Items/ItemComponent.cs b/Content.Server/GameObjects/Components/Items/ItemComponent.cs new file mode 100644 index 0000000000..113fc2c6ef --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/ItemComponent.cs @@ -0,0 +1,46 @@ +using Content.Server.Interfaces.GameObjects; +using SS14.Shared.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Server.Interfaces.GameObjects; +using System; + +namespace Content.Server.GameObjects +{ + public class ItemComponent : Component, IItemComponent + { + public override string Name => "Item"; + + /// + public IInventorySlot ContainingSlot { get; private set; } + + public void RemovedFromSlot() + { + if (ContainingSlot == null) + { + throw new InvalidOperationException("Item is not in a slot."); + } + + ContainingSlot = null; + + foreach (var component in Owner.GetComponents()) + { + component.Visible = true; + } + } + + public void EquippedToSlot(IInventorySlot slot) + { + if (ContainingSlot != null) + { + throw new InvalidOperationException("Item is already in a slot."); + } + + ContainingSlot = slot; + + foreach (var component in Owner.GetComponents()) + { + component.Visible = false; + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Items/ServerHandsComponent.cs b/Content.Server/GameObjects/Components/Items/ServerHandsComponent.cs new file mode 100644 index 0000000000..e5ea4c9572 --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/ServerHandsComponent.cs @@ -0,0 +1,293 @@ +using Content.Server.Interfaces.GameObjects; +using Content.Shared.GameObjects; +using SS14.Server.GameObjects.Events; +using SS14.Server.Interfaces.GameObjects; +using SS14.Shared; +using SS14.Shared.GameObjects; +using SS14.Shared.Utility; +using System; +using System.Collections.Generic; +using YamlDotNet.RepresentationModel; +using Lidgren.Network; + +namespace Content.Server.GameObjects +{ + public class HandsComponent : SharedHandsComponent, IHandsComponent + { + private string activeIndex; + public string ActiveIndex + { + get => activeIndex; + set + { + if (!hands.ContainsKey(value)) + { + throw new ArgumentException($"No hand '{value}'"); + } + + activeIndex = value; + } + } + + private Dictionary hands = new Dictionary(); + private List orderedHands = new List(); + private IInventoryComponent inventory; + private IServerTransformComponent transform; + private YamlMappingNode tempParametersMapping; + + // Mostly arbitrary. + public const float PICKUP_RANGE = 2; + + public override void Initialize() + { + inventory = Owner.GetComponent(); + transform = Owner.GetComponent(); + if (tempParametersMapping != null) + { + foreach (var node in tempParametersMapping.GetNode("hands")) + { + AddHand(node.AsString()); + } + } + + Owner.SubscribeEvent(OnKeyChange, this); + Owner.SubscribeEvent(OnClick, this); + base.Initialize(); + } + + public override void OnRemove() + { + inventory = null; + Owner.UnsubscribeEvent(this); + Owner.UnsubscribeEvent(this); + base.OnRemove(); + } + + public override void LoadParameters(YamlMappingNode mapping) + { + tempParametersMapping = mapping; + base.LoadParameters(mapping); + } + + public IEnumerable GetAllHeldItems() + { + foreach (var slot in hands.Values) + { + if (slot.Item != null) + { + yield return slot.Item; + } + } + } + + public IItemComponent GetHand(string index) + { + var slot = hands[index]; + return slot.Item; + } + + /// + /// Enumerates over the hand keys, returning the active hand first. + /// + private IEnumerable ActivePriorityEnumerable() + { + yield return ActiveIndex; + foreach (var hand in hands.Keys) + { + if (hand == ActiveIndex) + { + continue; + } + + yield return hand; + } + } + + public bool PutInHand(IItemComponent item) + { + foreach (var hand in ActivePriorityEnumerable()) + { + if (PutInHand(item, hand, fallback: false)) + { + return true; + } + } + + return false; + } + + public bool PutInHand(IItemComponent item, string index, bool fallback = true) + { + if (!CanPutInHand(item, index)) + { + return fallback && PutInHand(item); + } + + var slot = hands[index]; + return slot.Owner.Insert(slot.Name, item); + } + + public bool CanPutInHand(IItemComponent item) + { + foreach (var hand in ActivePriorityEnumerable()) + { + if (CanPutInHand(item, hand)) + { + return true; + } + } + + return false; + } + + public bool CanPutInHand(IItemComponent item, string index) + { + var slot = hands[index]; + return slot.Owner.CanInsert(slot.Name, item); + } + + public bool Drop(string index) + { + if (!CanDrop(index)) + { + return false; + } + + var slot = hands[index]; + return slot.Owner.Drop(slot.Name); + } + + public bool CanDrop(string index) + { + var slot = hands[index]; + return slot.Item != null && slot.Owner.CanDrop(slot.Name); + } + + public void AddHand(string index) + { + if (HasHand(index)) + { + throw new InvalidOperationException($"Hand '{index}' already exists."); + } + + var slot = inventory.AddSlot(HandSlotName(index)); + hands[index] = slot; + orderedHands.Add(index); + if (ActiveIndex == null) + { + ActiveIndex = index; + } + } + + public void RemoveHand(string index) + { + if (!HasHand(index)) + { + throw new InvalidOperationException($"Hand '{index}' does not exist."); + } + + inventory.RemoveSlot(HandSlotName(index)); + hands.Remove(index); + orderedHands.Remove(index); + + if (index == ActiveIndex) + { + if (orderedHands.Count == 0) + { + activeIndex = null; + } + else + { + activeIndex = orderedHands[0]; + } + } + } + + public bool HasHand(string index) + { + return hands.ContainsKey(index); + } + + /// + /// Get the name of the slot passed to the inventory component. + /// + private string HandSlotName(string index) => $"_hand_{index}"; + + public override ComponentState GetComponentState() + { + var dict = new Dictionary(hands.Count); + foreach (var hand in hands) + { + if (hand.Value.Item != null) + { + dict[hand.Key] = hand.Value.Item.Owner.Uid; + } + } + return new HandsComponentState(dict, ActiveIndex); + } + + // Game logic goes here. + public void OnKeyChange(object sender, EntityEventArgs uncast) + { + var cast = (BoundKeyChangeEventArgs)uncast; + if (cast.Actor != Owner || cast.KeyState != BoundKeyState.Down) + { + return; + } + + switch (cast.KeyFunction) + { + case BoundKeyFunctions.SwitchHands: + SwapHands(); + break; + case BoundKeyFunctions.Drop: + Drop(ActiveIndex); + break; + } + } + + private void SwapHands() + { + var index = orderedHands.FindIndex(x => x == ActiveIndex); + index++; + if (index >= orderedHands.Count) + { + index = 0; + } + + ActiveIndex = orderedHands[index]; + } + + public void OnClick(object sender, EntityEventArgs uncast) + { + var cast = (ClickedOnEntityEventArgs)uncast; + if (cast.MouseButton != MouseClickType.Left || Owner.EntityManager.GetEntity(cast.Clicker) != Owner) + { + return; + } + + var target = Owner.EntityManager.GetEntity(cast.Clicked); + var targetTransform = target.GetComponent(); + if (!target.TryGetComponent(out var item) || (targetTransform.WorldPosition - transform.WorldPosition).Length > PICKUP_RANGE) + { + return; + } + + PutInHand(item, ActiveIndex, fallback: false); + } + + public override void HandleNetworkMessage(IncomingEntityComponentMessage message, NetConnection sender) + { + if (message.MessageParameters.Count != 1) + { + return; + } + var index = message.MessageParameters[0]; + if (index is string newIndex && HasHand(newIndex)) + { + ActiveIndex = newIndex; + } + base.HandleNetworkMessage(message, sender); + } + } +} diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs new file mode 100644 index 0000000000..0d8fc902d4 --- /dev/null +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs @@ -0,0 +1,99 @@ +using SS14.Shared.Interfaces.GameObjects; +using System.Collections.Generic; + +namespace Content.Server.Interfaces.GameObjects +{ + public interface IHandsComponent : IComponent + { + /// + /// The hand index of the currently active hand. + /// + string ActiveIndex { get; set; } + + /// + /// Enumerates over every held item. + /// + IEnumerable GetAllHeldItems(); + + /// + /// Gets the item held by a hand. + /// + /// The index of the hand to get. + /// The item in the held, null if no item is held + IItemComponent GetHand(string index); + + /// + /// Puts an item into any empty hand, preferring the active hand. + /// + /// The item to put in a hand. + /// True if the item was inserted, false otherwise. + bool PutInHand(IItemComponent item); + + /// + /// Puts an item into a specific hand. + /// + /// The item to put in the hand. + /// The index of the hand to put the item into. + /// + /// If true and the provided hand is full, the method will fall back to + /// + /// True if the item was inserted into a hand, false otherwise. + bool PutInHand(IItemComponent item, string index, bool fallback=true); + + /// + /// Checks to see if an item can be put in any hand. + /// + /// The item to check for. + /// True if the item can be inserted, false otherwise. + bool CanPutInHand(IItemComponent item); + + /// + /// Checks to see if an item can be put in the specified hand. + /// + /// The item to check for. + /// The index for the hand to check for. + /// True if the item can be inserted, false otherwise. + bool CanPutInHand(IItemComponent item, string index); + + /// + /// Drops an item on the ground, removing it from the hand. + /// + /// The hand to drop from. + /// True if an item was successfully dropped, false otherwise. + bool Drop(string index); + + /// + /// Checks whether the item in the specified hand can be dropped. + /// + /// The hand to check for. + /// + /// True if the item can be dropped, false if the hand is empty or the item in the hand cannot be dropped. + /// + bool CanDrop(string index); + + /// + /// Adds a new hand to this hands component. + /// + /// The name of the hand to add. + /// + /// Thrown if a hand with specified name already exists. + /// + void AddHand(string index); + + /// + /// Removes a hand from this hands component. + /// + /// + /// If the hand contains an item, the item is dropped. + /// + /// The name of the hand to remove. + void RemoveHand(string index); + + /// + /// Checks whether a hand with the specified name exists. + /// + /// The hand name to check. + /// True if the hand exists, false otherwise. + bool HasHand(string index); + } +} diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IInventoryComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IInventoryComponent.cs new file mode 100644 index 0000000000..6fe70fc1b6 --- /dev/null +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IInventoryComponent.cs @@ -0,0 +1,101 @@ +using SS14.Server.Interfaces.GameObjects; +using SS14.Shared.Interfaces.GameObjects; +using System; + +namespace Content.Server.Interfaces.GameObjects +{ + public interface IInventoryComponent : IComponent + { + /// + /// Gets the item in the specified slot. + /// + /// The slot to get the item for. + /// Null if the slot is empty, otherwise the item. + IItemComponent Get(string slot); + + /// + /// Gets the slot with specified name. + /// This gets the slot, NOT the item contained therein. + /// + /// The name of the slot to get. + IInventorySlot GetSlot(string slot); + + /// + /// Puts an item in a slot. + /// + /// + /// This will fail if there is already an item in the specified slot. + /// + /// The slot to put the item in. + /// The item to insert into the slot. + /// True if the item was successfully inserted, false otherwise. + bool Insert(string slot, IItemComponent item); + + /// + /// Checks whether an item can be put in the specified slot. + /// + /// The slot to check for. + /// The item to check for. + /// True if the item can be inserted into the specified slot. + bool CanInsert(string slot, IItemComponent item); + + /// + /// Drops the item in a slot. + /// + /// The slot to drop the item from. + /// True if an item was dropped, false otherwise. + bool Drop(string slot); + + /// + /// Checks whether an item can be dropped from the specified slot. + /// + /// The slot to check for. + /// + /// True if there is an item in the slot and it can be dropped, false otherwise. + /// + bool CanDrop(string slot); + + /// + /// Adds a new slot to this inventory component. + /// + /// The name of the slot to add. + /// + /// Thrown if the slot with specified name already exists. + /// + IInventorySlot AddSlot(string slot); + + /// + /// Removes a slot from this inventory component. + /// + /// + /// If the slot contains an item, the item is dropped. + /// + /// The name of the slot to remove. + void RemoveSlot(string slot); + + /// + /// Checks whether a slot with the specified name exists. + /// + /// The slot name to check. + /// True if the slot exists, false otherwise. + bool HasSlot(string slot); + } + + public interface IInventorySlot + { + /// + /// The name of the slot. + /// + string Name { get; } + + /// + /// The item contained in the slot, can be null. + /// + IItemComponent Item { get; } + + /// + /// The component owning us. + /// + IInventoryComponent Owner { get; } + } +} diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IItemComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IItemComponent.cs new file mode 100644 index 0000000000..cd54ef375c --- /dev/null +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IItemComponent.cs @@ -0,0 +1,22 @@ +using SS14.Shared.Interfaces.GameObjects; + +namespace Content.Server.Interfaces.GameObjects +{ + public interface IItemComponent : IComponent + { + /// + /// The inventory slot this item is stored in, if any. + /// + IInventorySlot ContainingSlot { get; } + + /// + /// Called when the item is removed from its inventory slot. + /// + void RemovedFromSlot(); + + /// + /// Called when the item is inserted into a new inventory slot. + /// + void EquippedToSlot(IInventorySlot slot); + } +} diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index af0cea6ca3..c200d1c88f 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -56,6 +56,8 @@ + + @@ -95,4 +97,4 @@ - + \ No newline at end of file diff --git a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs new file mode 100644 index 0000000000..a6bd76d5d4 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs @@ -0,0 +1,27 @@ +using SS14.Shared.GameObjects; +using System; +using System.Collections.Generic; + +namespace Content.Shared.GameObjects +{ + public abstract class SharedHandsComponent : Component + { + public sealed override string Name => "Hands"; + public sealed override uint? NetID => ContentNetIDs.HANDS; + public sealed override Type StateType => typeof(HandsComponentState); + } + + // The IDs of the items get synced over the network. + [Serializable] + public class HandsComponentState : ComponentState + { + public readonly Dictionary Hands; + public readonly string ActiveIndex; + + public HandsComponentState(Dictionary hands, string activeIndex) : base(ContentNetIDs.HANDS) + { + Hands = hands; + ActiveIndex = activeIndex; + } + } +} diff --git a/Content.Shared/GameObjects/Components/NetIDs.cs b/Content.Shared/GameObjects/Components/NetIDs.cs new file mode 100644 index 0000000000..fe77227233 --- /dev/null +++ b/Content.Shared/GameObjects/Components/NetIDs.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.GameObjects +{ + public static class ContentNetIDs + { + public const uint HANDS = 1000; + } +} diff --git a/Resources/Prototypes/Entities/Items.yml b/Resources/Prototypes/Entities/Items.yml new file mode 100644 index 0000000000..28667c3eba --- /dev/null +++ b/Resources/Prototypes/Entities/Items.yml @@ -0,0 +1,14 @@ +- type: entity + name: "Toolbox 2: Handle edition" + parent: Toolbox + id: ToolboxItem + components: + - type: Item + +- type: entity + name: "Mop 2: Handle edition" + parent: Mop + id: MopItem + components: + - type: Item + diff --git a/Resources/Prototypes/Entities/Mobs.yml b/Resources/Prototypes/Entities/Mobs.yml new file mode 100644 index 0000000000..6e829fec7e --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs.yml @@ -0,0 +1,10 @@ +- type: entity + name: Urist McHands + id: HumanMob_Content + parent: HumanMob + components: + - type: Hands + hands: + - left + - right + - type: Inventory