Merge branch 'master' into replace-sounds-with-sound-specifier

# Conflicts:
#	Content.Server/Hands/Components/HandsComponent.cs
#	Content.Server/Light/Components/ExpendableLightComponent.cs
#	Content.Shared/Light/Component/SharedExpendableLightComponent.cs
This commit is contained in:
Galactic Chimp
2021-07-31 13:16:03 +02:00
105 changed files with 1483 additions and 1319 deletions

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic;
using Content.Client.Actions.Assignments;
using Content.Client.Actions.UI;
using Content.Client.Hands;
using Content.Client.Inventory;
using Content.Client.Items.UI;
using Content.Client.Items.Managers;
using Content.Shared.Actions.Components;
using Content.Shared.Actions.Prototypes;
using Robust.Client.GameObjects;
@@ -26,12 +25,13 @@ namespace Content.Client.Actions
public const byte Slots = 10;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
[ComponentDependency] private readonly HandsComponent? _handsComponent = null;
[ComponentDependency] private readonly ClientInventoryComponent? _inventoryComponent = null;
private ActionsUI? _ui;
private readonly List<ItemSlotButton> _highlightingItemSlots = new();
private EntityUid _highlightedEntity;
/// <summary>
/// Current assignments for all hotbars / slots for this entity.
@@ -225,26 +225,8 @@ namespace Content.Client.Actions
{
StopHighlightingItemSlots();
// figure out if it's in hand or inventory and highlight it
foreach (var hand in _handsComponent!.Gui!.Hands)
{
if (hand.HeldItem != item || hand.HandButton == null) continue;
_highlightingItemSlots.Add(hand.HandButton);
hand.HandButton.Highlight(true);
return;
}
foreach (var (slot, slotItem) in _inventoryComponent!.AllSlots)
{
if (slotItem != item) continue;
foreach (var itemSlotButton in
_inventoryComponent.InterfaceController.GetItemSlotButtons(slot))
{
_highlightingItemSlots.Add(itemSlotButton);
itemSlotButton.Highlight(true);
}
return;
}
_highlightedEntity = item.Uid;
_itemSlotManager.HighlightEntity(item.Uid);
}
/// <summary>
@@ -252,11 +234,11 @@ namespace Content.Client.Actions
/// </summary>
public void StopHighlightingItemSlots()
{
foreach (var itemSlot in _highlightingItemSlots)
{
itemSlot.Highlight(false);
}
_highlightingItemSlots.Clear();
if (_highlightedEntity == default)
return;
_itemSlotManager.UnHighlightEntity(_highlightedEntity);
_highlightedEntity = default;
}
public void ToggleActionsMenu()

View File

@@ -1,7 +1,7 @@
using Content.Shared.Clothing;
using Content.Shared.Clothing;
using Robust.Shared.GameObjects;
namespace Content.Client.Cloning
namespace Content.Client.Clothing
{
[RegisterComponent]
public sealed class MagbootsComponent : SharedMagbootsComponent

View File

@@ -81,7 +81,6 @@ namespace Content.Client.Entry
factory.RegisterClass<SharedCargoConsoleComponent>();
factory.RegisterClass<SharedReagentDispenserComponent>();
factory.RegisterClass<SharedChemMasterComponent>();
factory.RegisterClass<SharedMicrowaveComponent>();
factory.RegisterClass<SharedGravityGeneratorComponent>();
factory.RegisterClass<SharedAMEControllerComponent>();

View File

@@ -0,0 +1,3 @@
<Control xmlns="https://spacestation14.io">
<Label StyleClasses="ItemStatus" Text="Pulling" />
</Control>

View File

@@ -0,0 +1,13 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Hands
{
public sealed class HandVirtualPullItemStatus : Control
{
public HandVirtualPullItemStatus()
{
RobustXamlLoader.Load(this);
}
}
}

View File

@@ -1,15 +1,8 @@
using System.Collections.Generic;
using Content.Client.Animations;
using Content.Client.HUD;
using Content.Shared.Hands.Components;
using Content.Shared.Item;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Client.Hands
{
@@ -18,16 +11,7 @@ namespace Content.Client.Hands
[ComponentReference(typeof(SharedHandsComponent))]
public class HandsComponent : SharedHandsComponent
{
[Dependency] private readonly IGameHud _gameHud = default!;
[ViewVariables]
public HandsGui? Gui { get; private set; }
protected override void OnRemove()
{
ClearGui();
base.OnRemove();
}
public HandsGui? Gui { get; set; }
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
@@ -38,94 +22,23 @@ namespace Content.Client.Hands
foreach (var handState in state.Hands)
{
var newHand = new Hand(handState.Name, handState.Enabled, handState.Location);
var newHand = new Hand(handState.Name, handState.Location);
Hands.Add(newHand);
}
ActiveHand = state.ActiveHand;
UpdateHandContainers();
UpdateHandVisualizer();
UpdateHandsGuiState();
}
public void SettupGui()
{
if (Gui == null)
{
Gui = new HandsGui();
_gameHud.HandsContainer.AddChild(Gui);
Gui.HandClick += args => OnHandClick(args.HandClicked);
Gui.HandActivate += args => OnActivateInHand(args.HandUsed);
UpdateHandsGuiState();
}
}
public void ClearGui()
{
Gui?.Dispose();
Gui = null;
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, netChannel, session);
switch (message)
{
case PickupAnimationMessage msg:
RunPickupAnimation(msg);
break;
}
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this });
}
public override void HandsModified()
{
base.HandsModified();
UpdateHandContainers();
UpdateHandVisualizer();
UpdateHandsGuiState();
}
private void OnHandClick(string handClicked)
{
if (!TryGetHand(handClicked, out var pressedHand))
return;
if (!TryGetActiveHand(out var activeHand))
return;
var pressedEntity = pressedHand.HeldEntity;
var activeEntity = activeHand.HeldEntity;
if (pressedHand == activeHand && activeEntity != null)
{
SendNetworkMessage(new UseInHandMsg()); //use item in hand
return;
}
if (pressedHand != activeHand && pressedEntity == null)
{
SendNetworkMessage(new ClientChangedHandMsg(pressedHand.Name)); //swap hand
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity != null)
{
SendNetworkMessage(new ClientAttackByInHandMsg(pressedHand.Name)); //use active item on held item
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity == null)
{
SendNetworkMessage(new MoveItemFromHandMsg(pressedHand.Name)); //move item in hand to active hand
return;
}
}
private void OnActivateInHand(string handActivated)
{
SendNetworkMessage(new ActivateInHandMsg(handActivated));
base.HandsModified();
}
public void UpdateHandContainers()
@@ -149,27 +62,10 @@ namespace Content.Client.Hands
appearance.SetData(HandsVisuals.VisualState, GetHandsVisualState());
}
public void UpdateHandsGuiState()
{
Gui?.SetState(GetHandsGuiState());
}
private HandsGuiState GetHandsGuiState()
{
var handStates = new List<GuiHand>();
foreach (var hand in ReadOnlyHands)
{
var handState = new GuiHand(hand.Name, hand.Location, hand.HeldEntity, hand.Enabled);
handStates.Add(handState);
}
return new HandsGuiState(handStates, ActiveHand);
}
private HandsVisualState GetHandsVisualState()
{
var hands = new List<HandVisualState>();
foreach (var hand in ReadOnlyHands)
foreach (var hand in Hands)
{
if (hand.HeldEntity == null)
continue;
@@ -182,16 +78,5 @@ namespace Content.Client.Hands
}
return new(hands);
}
private void RunPickupAnimation(PickupAnimationMessage msg)
{
if (!Owner.EntityManager.TryGetEntity(msg.EntityUid, out var entity))
return;
if (!IoCManager.Resolve<IGameTiming>().IsFirstTimePredicted)
return;
ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection);
}
}
}

View File

@@ -0,0 +1,6 @@
<Control xmlns="https://spacestation14.io">
<BoxContainer Orientation="Vertical">
<Control Name="StatusContainer" />
<BoxContainer Name="HandsContainer" Orientation="Horizontal" HorizontalAlignment="Center" />
</BoxContainer>
</Control>

View File

@@ -1,87 +1,84 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.HUD;
using Content.Client.Items.Managers;
using Content.Client.Items.UI;
using Content.Client.Resources;
using Content.Shared;
using Content.Shared.CCVar;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Hands
{
public class HandsGui : Control
[GenerateTypedNameReferences]
public sealed partial class HandsGui : Control
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
[Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
private readonly HandsSystem _handsSystem;
private readonly HandsComponent _handsComponent;
private Texture StorageTexture => _gameHud.GetHudTexture("back.png");
private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Inventory/blocked.png");
private ItemStatusPanel StatusPanel { get; }
private BoxContainer HandsContainer { get; }
[ViewVariables]
public IReadOnlyList<GuiHand> Hands => _hands;
private List<GuiHand> _hands = new();
[ViewVariables] private GuiHand[] _hands = Array.Empty<GuiHand>();
private string? ActiveHand { get; set; }
public Action<HandClickEventArgs>? HandClick; //TODO: Move to Eventbus
public Action<HandActivateEventArgs>? HandActivate; //TODO: Move to Eventbus
public HandsGui()
public HandsGui(HandsComponent hands, HandsSystem handsSystem)
{
IoCManager.InjectDependencies(this);
_configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
_handsComponent = hands;
_handsSystem = handsSystem;
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
SeparationOverride = 0,
HorizontalAlignment = HAlignment.Center,
Children =
{
new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
(StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle)),
(HandsContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalAlignment = HAlignment.Center
}),
}
},
}
});
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle);
StatusContainer.AddChild(StatusPanel);
StatusPanel.SetPositionFirst();
}
public void SetState(HandsGuiState state)
protected override void EnteredTree()
{
base.EnteredTree();
_handsSystem.GuiStateUpdated += HandsSystemOnGuiStateUpdated;
_configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme);
HandsSystemOnGuiStateUpdated();
}
protected override void ExitedTree()
{
base.ExitedTree();
_handsSystem.GuiStateUpdated -= HandsSystemOnGuiStateUpdated;
_configManager.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme);
}
private void HandsSystemOnGuiStateUpdated()
{
var state = _handsSystem.GetGuiState();
ActiveHand = state.ActiveHand;
_hands = state.GuiHands;
Array.Sort(_hands, HandOrderComparer.Instance);
UpdateGui();
}
@@ -97,12 +94,15 @@ namespace Content.Client.Hands
var handName = hand.Name;
newButton.OnPressed += args => OnHandPressed(args, handName);
newButton.OnStoragePressed += args => OnStoragePressed(handName);
newButton.Blocked.Visible = !hand.Enabled;
newButton.OnStoragePressed += _ => OnStoragePressed(handName);
_itemSlotManager.SetItemSlot(newButton, hand.HeldItem);
// Show blocked overlay if hand is pulling.
newButton.Blocked.Visible =
hand.HeldItem != null && hand.HeldItem.HasComponent<HandVirtualPullComponent>();
}
if (TryGetActiveHand(out var activeHand))
{
activeHand.HandButton.SetActiveHand(true);
@@ -114,7 +114,7 @@ namespace Content.Client.Hands
{
if (args.Function == EngineKeyFunctions.UIClick)
{
HandClick?.Invoke(new HandClickEventArgs(handName));
_handsSystem.UIHandClick(_handsComponent, handName);
}
else if (TryGetHand(handName, out var hand))
{
@@ -124,7 +124,7 @@ namespace Content.Client.Hands
private void OnStoragePressed(string handName)
{
HandActivate?.Invoke(new HandActivateEventArgs(handName));
_handsSystem.UIHandActivate(handName);
}
private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand)
@@ -145,6 +145,7 @@ namespace Content.Client.Hands
if (hand.Name == handName)
foundHand = hand;
}
return foundHand != null;
}
@@ -153,7 +154,9 @@ namespace Content.Client.Hands
base.FrameUpdate(args);
foreach (var hand in _hands)
{
_itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem);
}
}
private HandButton MakeHandButton(HandLocation buttonLocation)
@@ -173,23 +176,31 @@ namespace Content.Client.Hands
UpdateGui();
}
public class HandClickEventArgs
private sealed class HandOrderComparer : IComparer<GuiHand>
{
public string HandClicked { get; }
public static readonly HandOrderComparer Instance = new();
public HandClickEventArgs(string handClicked)
public int Compare(GuiHand? x, GuiHand? y)
{
HandClicked = handClicked;
}
}
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
public class HandActivateEventArgs
{
public string HandUsed { get; }
var orderX = Map(x.HandLocation);
var orderY = Map(y.HandLocation);
public HandActivateEventArgs(string handUsed)
{
HandUsed = handUsed;
return orderX.CompareTo(orderY);
static int Map(HandLocation loc)
{
return loc switch
{
HandLocation.Left => 3,
HandLocation.Middle => 2,
HandLocation.Right => 1,
_ => throw new ArgumentOutOfRangeException(nameof(loc), loc, null)
};
}
}
}
}
@@ -203,7 +214,7 @@ namespace Content.Client.Hands
/// The set of hands to be displayed.
/// </summary>
[ViewVariables]
public List<GuiHand> GuiHands { get; } = new();
public GuiHand[] GuiHands { get; }
/// <summary>
/// The name of the currently active hand.
@@ -211,7 +222,7 @@ namespace Content.Client.Hands
[ViewVariables]
public string? ActiveHand { get; }
public HandsGuiState(List<GuiHand> guiHands, string? activeHand = null)
public HandsGuiState(GuiHand[] guiHands, string? activeHand = null)
{
GuiHands = guiHands;
ActiveHand = activeHand;
@@ -247,18 +258,11 @@ namespace Content.Client.Hands
[ViewVariables]
public HandButton HandButton { get; set; } = default!;
/// <summary>
/// If this hand can be used by the player.
/// </summary>
[ViewVariables]
public bool Enabled { get; }
public GuiHand(string name, HandLocation handLocation, IEntity? heldItem, bool enabled)
public GuiHand(string name, HandLocation handLocation, IEntity? heldItem)
{
Name = name;
HandLocation = handLocation;
HeldItem = heldItem;
Enabled = enabled;
}
}
}

View File

@@ -1,80 +0,0 @@
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Robust.Client.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
using Robust.Shared.Players;
namespace Content.Client.Hands
{
internal sealed class HandsSystem : SharedHandsSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HandsComponent, PlayerAttachedEvent>((_, component, _) => component.SettupGui());
SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>((_, component, _) => component.ClearGui());
CommandBinds.Builder
.Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed))
.Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed))
.Register<HandsSystem>();
}
public override void Shutdown()
{
CommandBinds.Unregister<HandsSystem>();
base.Shutdown();
}
private void SwapHandsPressed(ICommonSession? session)
{
if (session == null)
return;
var player = session.AttachedEntity;
if (player == null)
return;
if (!player.TryGetComponent(out SharedHandsComponent? hands))
return;
if (!hands.TryGetSwapHandsResult(out var nextHand))
return;
EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(nextHand));
}
private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (session == null)
return false;
var player = session.AttachedEntity;
if (player == null)
return false;
if (!player.TryGetComponent(out SharedHandsComponent? hands))
return false;
var activeHand = hands.ActiveHand;
if (activeHand == null)
return false;
EntityManager.RaisePredictiveEvent(new RequestDropHeldEntityEvent(activeHand, coords));
return true;
}
protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args)
{
component.HandsModified();
}
}
}

View File

@@ -0,0 +1,18 @@
using Content.Client.Items;
using Content.Shared.Hands.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Client.Hands
{
[UsedImplicitly]
public sealed class HandVirtualPullSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<HandVirtualPullComponent>(_ => new HandVirtualPullItemStatus());
}
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Linq;
using Content.Client.Animations;
using Content.Client.HUD;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Content.Client.Hands
{
[UsedImplicitly]
public sealed class HandsSystem : SharedHandsSystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action? GuiStateUpdated;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HandsComponent, PlayerAttachedEvent>(HandlePlayerAttached);
SubscribeLocalEvent<HandsComponent, PlayerDetachedEvent>(HandlePlayerDetached);
SubscribeLocalEvent<HandsComponent, ComponentRemove>(HandleCompRemove);
SubscribeLocalEvent<HandsModifiedMessage>(HandleHandsModified);
SubscribeNetworkEvent<PickupAnimationMessage>(HandlePickupAnimation);
}
public override void Shutdown()
{
CommandBinds.Unregister<HandsSystem>();
base.Shutdown();
}
private void HandleHandsModified(HandsModifiedMessage ev)
{
if (ev.Hands.Owner == _playerManager.LocalPlayer?.ControlledEntity)
GuiStateUpdated?.Invoke();
}
protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args)
{
if (uid == _playerManager.LocalPlayer?.ControlledEntity?.Uid)
GuiStateUpdated?.Invoke();
}
private void HandlePickupAnimation(PickupAnimationMessage msg)
{
if (!EntityManager.TryGetEntity(msg.EntityUid, out var entity))
return;
if (!_gameTiming.IsFirstTimePredicted)
return;
ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection);
}
public HandsGuiState GetGuiState()
{
var player = _playerManager.LocalPlayer?.ControlledEntity;
if (player == null || !player.TryGetComponent(out HandsComponent? hands))
return new HandsGuiState(Array.Empty<GuiHand>());
var states = hands.Hands
.Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity))
.ToArray();
return new HandsGuiState(states, hands.ActiveHand);
}
public void UIHandClick(HandsComponent hands, string handName)
{
if (!hands.TryGetHand(handName, out var pressedHand))
return;
if (!hands.TryGetActiveHand(out var activeHand))
return;
var pressedEntity = pressedHand.HeldEntity;
var activeEntity = activeHand.HeldEntity;
if (pressedHand == activeHand && activeEntity != null)
{
// use item in hand
// it will always be attack_self() in my heart.
RaiseNetworkEvent(new UseInHandMsg());
return;
}
if (pressedHand != activeHand && pressedEntity == null)
{
// change active hand
RaiseNetworkEvent(new RequestSetHandEvent(handName));
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity != null)
{
// use active item on held item
RaiseNetworkEvent(new ClientInteractUsingInHandMsg(pressedHand.Name));
return;
}
if (pressedHand != activeHand && pressedEntity != null && activeEntity == null)
{
// use active item on held item
RaiseNetworkEvent(new MoveItemFromHandMsg(pressedHand.Name));
}
}
public void UIHandActivate(string handName)
{
RaiseNetworkEvent (new ActivateInHandMsg(handName));
}
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, PlayerAttachedEvent args)
{
component.Gui = new HandsGui(component, this);
_gameHud.HandsContainer.AddChild(component.Gui);
}
private static void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args)
{
ClearGui(component);
}
private static void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args)
{
ClearGui(component);
}
private static void ClearGui(HandsComponent comp)
{
comp.Gui?.Orphan();
comp.Gui = null;
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
namespace Content.Client.Items
{
public sealed class ItemStatusCollectMessage : EntityEventArgs
{
public List<Control> Controls = new();
}
public static class ItemStatusRegisterExt
{
/// <summary>
/// Register an item status control for a component.
/// </summary>
/// <param name="subs">The <see cref="EntitySystem.Subs"/> handle from within entity system initialize.</param>
/// <param name="createControl">A delegate to create the actual control.</param>
/// <typeparam name="TComp">The type of component for which this control should be made.</typeparam>
public static void ItemStatus<TComp>(
this EntitySystem.Subscriptions subs,
Func<EntityUid, Control> createControl)
where TComp : IComponent
{
subs.SubscribeLocalEvent<TComp, ItemStatusCollectMessage>((uid, _, args) =>
{
args.Controls.Add(createControl(uid));
});
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Client.Items.UI;
using System;
using Content.Client.Items.UI;
using Robust.Client.UserInterface;
using Robust.Shared.GameObjects;
@@ -10,5 +11,21 @@ namespace Content.Client.Items.Managers
void UpdateCooldown(ItemSlotButton? cooldownTexture, IEntity? entity);
bool SetItemSlot(ItemSlotButton button, IEntity? entity);
void HoverInSlot(ItemSlotButton button, IEntity? entity, bool fits);
event Action<EntitySlotHighlightedEventArgs>? EntityHighlightedUpdated;
bool IsHighlighted(EntityUid uid);
/// <summary>
/// Highlight all slot controls that contain the specified entity.
/// </summary>
/// <param name="uid">The UID of the entity to highlight.</param>
/// <seealso cref="UnHighlightEntity"/>
void HighlightEntity(EntityUid uid);
/// <summary>
/// Remove highlighting for the specified entity.
/// </summary>
/// <param name="uid">The UID of the entity to unhighlight.</param>
/// <seealso cref="HighlightEntity"/>
void UnHighlightEntity(EntityUid uid);
}
}

View File

@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using Content.Client.Examine;
using Content.Client.Items.UI;
using Content.Client.Storage;
using Content.Client.Verbs;
using Content.Shared.Cooldown;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -28,6 +31,11 @@ namespace Content.Client.Items.Managers
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
private readonly HashSet<EntityUid> _highlightEntities = new();
public event Action<EntitySlotHighlightedEventArgs>? EntityHighlightedUpdated;
public bool SetItemSlot(ItemSlotButton button, IEntity? entity)
{
@@ -38,13 +46,26 @@ namespace Content.Client.Items.Managers
}
else
{
if (!entity.TryGetComponent(out ISpriteComponent? sprite))
ISpriteComponent? sprite;
if (entity.TryGetComponent(out HandVirtualPullComponent? virtPull)
&& _componentManager.TryGetComponent(virtPull.PulledEntity, out ISpriteComponent pulledSprite))
{
sprite = pulledSprite;
}
else if (!entity.TryGetComponent(out sprite))
{
return false;
}
button.ClearHover();
button.SpriteView.Sprite = sprite;
button.StorageButton.Visible = entity.HasComponent<ClientStorageComponent>();
}
button.Entity = entity?.Uid ?? default;
// im lazy
button.UpdateSlotHighlighted();
return true;
}
@@ -145,5 +166,38 @@ namespace Content.Client.Items.Managers
button.HoverSpriteView.Sprite = hoverSprite;
}
public bool IsHighlighted(EntityUid uid)
{
return _highlightEntities.Contains(uid);
}
public void HighlightEntity(EntityUid uid)
{
if (!_highlightEntities.Add(uid))
return;
EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, true));
}
public void UnHighlightEntity(EntityUid uid)
{
if (!_highlightEntities.Remove(uid))
return;
EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, false));
}
}
public readonly struct EntitySlotHighlightedEventArgs
{
public EntitySlotHighlightedEventArgs(EntityUid entity, bool newHighlighted)
{
Entity = entity;
NewHighlighted = newHighlighted;
}
public EntityUid Entity { get; }
public bool NewHighlighted { get; }
}
}

View File

@@ -1,18 +1,24 @@
using System;
using Content.Client.Cooldown;
using Content.Client.Items.Managers;
using Content.Client.Stylesheets;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Content.Client.Items.UI
{
public class ItemSlotButton : Control
public class ItemSlotButton : Control, IEntityEventSubscriber
{
private const string HighlightShader = "SelectionOutlineInrange";
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
public EntityUid Entity { get; set; }
public TextureRect Button { get; }
public SpriteView SpriteView { get; }
public SpriteView HoverSpriteView { get; }
@@ -32,6 +38,8 @@ namespace Content.Client.Items.UI
public ItemSlotButton(Texture texture, Texture storageTexture, string textureName)
{
IoCManager.InjectDependencies(this);
MinSize = (64, 64);
TextureName = textureName;
@@ -101,6 +109,31 @@ namespace Content.Client.Items.UI
});
}
protected override void EnteredTree()
{
base.EnteredTree();
_itemSlotManager.EntityHighlightedUpdated += HandleEntitySlotHighlighted;
UpdateSlotHighlighted();
}
protected override void ExitedTree()
{
base.ExitedTree();
_itemSlotManager.EntityHighlightedUpdated -= HandleEntitySlotHighlighted;
}
private void HandleEntitySlotHighlighted(EntitySlotHighlightedEventArgs entitySlotHighlightedEventArgs)
{
UpdateSlotHighlighted();
}
public void UpdateSlotHighlighted()
{
Highlight(_itemSlotManager.IsHighlighted(Entity));
}
public void ClearHover()
{
if (EntityHover)

View File

@@ -8,7 +8,9 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Content.Client.IoC.StaticIoC;
@@ -18,6 +20,8 @@ namespace Content.Client.Items.UI
{
public class ItemStatusPanel : Control
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables]
private readonly List<(IItemStatus, Control)> _activeStatusComponents = new();
@@ -33,6 +37,8 @@ namespace Content.Client.Items.UI
public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign)
{
IoCManager.InjectDependencies(this);
var panel = new StyleBoxTexture
{
Texture = texture
@@ -117,6 +123,13 @@ namespace Content.Client.Items.UI
return new ItemStatusPanel(ResC.GetTexture(texture), cutOut, flat, textAlign);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateItemName();
}
public void Update(IEntity? entity)
{
if (entity == null)
@@ -131,12 +144,29 @@ namespace Content.Client.Items.UI
{
_entity = entity;
BuildNewEntityStatus();
_itemNameLabel.Text = entity.Name;
UpdateItemName();
}
_panel.Visible = true;
}
private void UpdateItemName()
{
if (_entity == null)
return;
if (_entity.TryGetComponent(out HandVirtualPullComponent? virtualPull)
&& _entityManager.TryGetEntity(virtualPull.PulledEntity, out var pulledEnt))
{
_itemNameLabel.Text = pulledEnt.Name;
}
else
{
_itemNameLabel.Text = _entity.Name;
}
}
private void ClearOldStatus()
{
_statusContents.RemoveAllChildren();
@@ -162,6 +192,14 @@ namespace Content.Client.Items.UI
_activeStatusComponents.Add((statusComponent, control));
}
var collectMsg = new ItemStatusCollectMessage();
_entity.EntityManager.EventBus.RaiseLocalEvent(_entity.Uid, collectMsg);
foreach (var control in collectMsg.Controls)
{
_statusContents.AddChild(control);
}
}
}
}

View File

@@ -2,7 +2,7 @@ using Content.Shared.DragDrop;
using Content.Shared.Kitchen.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.Kitchen
namespace Content.Client.Kitchen.Components
{
[RegisterComponent]
internal sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent

View File

@@ -0,0 +1,17 @@
using Content.Shared.Kitchen.Components;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Kitchen.Components
{
[RegisterComponent]
public class MicrowaveComponent : SharedMicrowaveComponent
{
public IPlayingAudioStream? PlayingStream { get; set; }
[DataField("loopingSound")]
public SoundSpecifier LoopingSound = new SoundPathSpecifier("/Audio/Machines/microwave_loop.ogg");
}
}

View File

@@ -0,0 +1,31 @@
using System;
using Content.Client.Kitchen.Components;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Content.Client.Kitchen.EntitySystems
{
public class MicrowaveSystem : EntitySystem
{
public void StartSoundLoop(MicrowaveComponent microwave)
{
StopSoundLoop(microwave);
microwave.PlayingStream = SoundSystem.Play(Filter.Local(), microwave.LoopingSound.GetSound(), microwave.Owner,
AudioParams.Default.WithAttenuation(1).WithMaxDistance(5).WithLoop(true));
}
public void StopSoundLoop(MicrowaveComponent microwave)
{
try
{
microwave.PlayingStream?.Stop();
}
catch (Exception _)
{
// TODO: HOLY SHIT EXPOSE SOME DISPOSED PROPERTY ON PLAYING STREAM OR SOMETHING.
}
}
}
}

View File

@@ -1,10 +1,10 @@
using Content.Client.Sound;
using Content.Client.Kitchen.Components;
using Content.Client.Kitchen.EntitySystems;
using Content.Shared.Kitchen.Components;
using Content.Shared.Power;
using Content.Shared.Sound;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
namespace Content.Client.Kitchen.Visualizers
@@ -17,35 +17,33 @@ namespace Content.Client.Kitchen.Visualizers
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
var loopingSoundComponent = component.Owner.GetComponentOrNull<LoopingSoundComponent>();
var microwaveComponent = component.Owner.GetComponentOrNull<MicrowaveComponent>();
if (!component.TryGetData(PowerDeviceVisuals.VisualState, out MicrowaveVisualState state))
{
state = MicrowaveVisualState.Idle;
}
// The only reason we get the entity system so late is so that tests don't fail... Amazing, huh?
switch (state)
{
case MicrowaveVisualState.Broken:
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mwb");
loopingSoundComponent?.StopAllSounds();
if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StopSoundLoop(microwaveComponent);
break;
case MicrowaveVisualState.Idle:
sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw");
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_unlit");
loopingSoundComponent?.StopAllSounds();
if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StopSoundLoop(microwaveComponent);
break;
case MicrowaveVisualState.Cooking:
sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw");
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_running_unlit");
var audioParams = AudioParams.Default;
audioParams.Loop = true;
var scheduledSound = new ScheduledSound();
scheduledSound.Filename = "/Audio/Machines/microwave_loop.ogg";
scheduledSound.AudioParams = audioParams;
loopingSoundComponent?.StopAllSounds();
loopingSoundComponent?.AddScheduledSound(scheduledSound);
if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StartSoundLoop(microwaveComponent);
break;
default:

View File

@@ -1,4 +1,5 @@
using Content.Shared.Light.Component;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
namespace Content.Client.Light.Components
@@ -9,6 +10,6 @@ namespace Content.Client.Light.Components
[RegisterComponent]
public class ExpendableLightComponent : SharedExpendableLightComponent
{
public IPlayingAudioStream? PlayingStream { get; set; }
}
}

View File

@@ -1,7 +1,10 @@
using Content.Client.Light.Components;
using System;
using Content.Client.Light.Components;
using Content.Shared.Light.Component;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Client.Light.Visualizers
{
@@ -17,7 +20,7 @@ namespace Content.Client.Light.Visualizers
return;
}
if (component.TryGetData(ExpendableLightVisuals.State, out string lightBehaviourID))
if (component.TryGetData(ExpendableLightVisuals.Behavior, out string lightBehaviourID))
{
if (component.Owner.TryGetComponent<LightBehaviourComponent>(out var lightBehaviour))
{
@@ -33,6 +36,35 @@ namespace Content.Client.Light.Visualizers
}
}
}
void TryStopStream(IPlayingAudioStream? stream)
{
try
{
stream?.Stop();
}
catch (Exception _)
{
// TODO: HOLY SHIT EXPOSE SOME DISPOSED PROPERTY ON PLAYING STREAM OR SOMETHING.
}
}
if (component.TryGetData(ExpendableLightVisuals.State, out ExpendableLightState state)
&& component.Owner.TryGetComponent<ExpendableLightComponent>(out var expendableLight))
{
switch (state)
{
case ExpendableLightState.Lit:
TryStopStream(expendableLight.PlayingStream);
expendableLight.PlayingStream = SoundSystem.Play(Filter.Local(), expendableLight.LoopedSound,
expendableLight.Owner, SharedExpendableLightComponent.LoopedSoundParams.WithLoop(true));
break;
case ExpendableLightState.Dead:
TryStopStream(expendableLight.PlayingStream);
break;
}
}
}
}
}

View File

@@ -1,105 +0,0 @@
using System.Collections.Generic;
using Content.Shared.Physics;
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Sound
{
[RegisterComponent]
public class LoopingSoundComponent : SharedLoopingSoundComponent
{
[Dependency] private readonly IRobustRandom _random = default!;
private readonly Dictionary<ScheduledSound, IPlayingAudioStream> _audioStreams = new();
[DataField("schedules", true)]
private List<ScheduledSound> _scheduledSounds
{
set => value.ForEach(AddScheduledSound);
get => new();
}
public override void StopAllSounds()
{
foreach (var kvp in _audioStreams)
{
kvp.Key.Play = false;
kvp.Value.Stop();
}
_audioStreams.Clear();
}
public override void StopScheduledSound(string filename)
{
foreach (var kvp in _audioStreams)
{
if (kvp.Key.Filename != filename) continue;
kvp.Key.Play = false;
kvp.Value.Stop();
_audioStreams.Remove(kvp.Key);
}
}
public override void AddScheduledSound(ScheduledSound schedule)
{
Play(schedule);
}
public void Play(ScheduledSound schedule)
{
if (!schedule.Play) return;
Owner.SpawnTimer((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() =>
{
if (!schedule.Play) return; // We make sure this hasn't changed.
if (!_audioStreams.ContainsKey(schedule))
{
_audioStreams.Add(schedule, SoundSystem.Play(Filter.Local(), schedule.Filename, Owner, schedule.AudioParams)!);
}
else
{
_audioStreams[schedule] = SoundSystem.Play(Filter.Local(), schedule.Filename, Owner, schedule.AudioParams)!;
}
if (schedule.Times == 0) return;
if (schedule.Times > 0) schedule.Times--;
Play(schedule);
});
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case ScheduledSoundMessage msg:
AddScheduledSound(msg.Schedule);
break;
case StopSoundScheduleMessage msg:
StopScheduledSound(msg.Filename);
break;
case StopAllSoundsMessage _:
StopAllSounds();
break;
}
}
protected override void Initialize()
{
base.Initialize();
SoundSystem.OcclusionCollisionMask = (int) CollisionGroup.Impassable;
}
}
}