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

View File

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

View File

@@ -81,7 +81,6 @@ namespace Content.Client.Entry
factory.RegisterClass<SharedCargoConsoleComponent>(); factory.RegisterClass<SharedCargoConsoleComponent>();
factory.RegisterClass<SharedReagentDispenserComponent>(); factory.RegisterClass<SharedReagentDispenserComponent>();
factory.RegisterClass<SharedChemMasterComponent>(); factory.RegisterClass<SharedChemMasterComponent>();
factory.RegisterClass<SharedMicrowaveComponent>();
factory.RegisterClass<SharedGravityGeneratorComponent>(); factory.RegisterClass<SharedGravityGeneratorComponent>();
factory.RegisterClass<SharedAMEControllerComponent>(); 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 System.Collections.Generic;
using Content.Client.Animations;
using Content.Client.HUD;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Item; using Content.Shared.Item;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; 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 namespace Content.Client.Hands
{ {
@@ -18,16 +11,7 @@ namespace Content.Client.Hands
[ComponentReference(typeof(SharedHandsComponent))] [ComponentReference(typeof(SharedHandsComponent))]
public class HandsComponent : SharedHandsComponent public class HandsComponent : SharedHandsComponent
{ {
[Dependency] private readonly IGameHud _gameHud = default!; public HandsGui? Gui { get; set; }
[ViewVariables]
public HandsGui? Gui { get; private set; }
protected override void OnRemove()
{
ClearGui();
base.OnRemove();
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{ {
@@ -38,94 +22,23 @@ namespace Content.Client.Hands
foreach (var handState in state.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); Hands.Add(newHand);
} }
ActiveHand = state.ActiveHand; ActiveHand = state.ActiveHand;
UpdateHandContainers(); UpdateHandContainers();
UpdateHandVisualizer(); UpdateHandVisualizer();
UpdateHandsGuiState(); Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this });
}
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;
}
} }
public override void HandsModified() public override void HandsModified()
{ {
base.HandsModified();
UpdateHandContainers(); UpdateHandContainers();
UpdateHandVisualizer(); UpdateHandVisualizer();
UpdateHandsGuiState();
}
private void OnHandClick(string handClicked) base.HandsModified();
{
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));
} }
public void UpdateHandContainers() public void UpdateHandContainers()
@@ -149,27 +62,10 @@ namespace Content.Client.Hands
appearance.SetData(HandsVisuals.VisualState, GetHandsVisualState()); 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() private HandsVisualState GetHandsVisualState()
{ {
var hands = new List<HandVisualState>(); var hands = new List<HandVisualState>();
foreach (var hand in ReadOnlyHands) foreach (var hand in Hands)
{ {
if (hand.HeldEntity == null) if (hand.HeldEntity == null)
continue; continue;
@@ -182,16 +78,5 @@ namespace Content.Client.Hands
} }
return new(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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Client.HUD; using Content.Client.HUD;
using Content.Client.Items.Managers; using Content.Client.Items.Managers;
using Content.Client.Items.UI; using Content.Client.Items.UI;
using Content.Client.Resources; using Content.Client.Resources;
using Content.Shared;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Input; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Input; using Robust.Shared.Input;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Hands namespace Content.Client.Hands
{ {
public class HandsGui : Control [GenerateTypedNameReferences]
public sealed partial class HandsGui : Control
{ {
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!; [Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
[Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IGameHud _gameHud = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!; [Dependency] private readonly INetConfigurationManager _configManager = default!;
private readonly HandsSystem _handsSystem;
private readonly HandsComponent _handsComponent;
private Texture StorageTexture => _gameHud.GetHudTexture("back.png"); private Texture StorageTexture => _gameHud.GetHudTexture("back.png");
private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Inventory/blocked.png"); private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Inventory/blocked.png");
private ItemStatusPanel StatusPanel { get; } private ItemStatusPanel StatusPanel { get; }
private BoxContainer HandsContainer { get; } [ViewVariables] private GuiHand[] _hands = Array.Empty<GuiHand>();
[ViewVariables]
public IReadOnlyList<GuiHand> Hands => _hands;
private List<GuiHand> _hands = new();
private string? ActiveHand { get; set; } private string? ActiveHand { get; set; }
public Action<HandClickEventArgs>? HandClick; //TODO: Move to Eventbus public HandsGui(HandsComponent hands, HandsSystem handsSystem)
public Action<HandActivateEventArgs>? HandActivate; //TODO: Move to Eventbus
public HandsGui()
{ {
IoCManager.InjectDependencies(this); _handsComponent = hands;
_configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme); _handsSystem = handsSystem;
AddChild(new BoxContainer RobustXamlLoader.Load(this);
{ IoCManager.InjectDependencies(this);
Orientation = LayoutOrientation.Horizontal,
SeparationOverride = 0, StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle);
HorizontalAlignment = HAlignment.Center, StatusContainer.AddChild(StatusPanel);
Children = StatusPanel.SetPositionFirst();
{
new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
Children =
{
(StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle)),
(HandsContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalAlignment = HAlignment.Center
}),
}
},
}
});
} }
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; ActiveHand = state.ActiveHand;
_hands = state.GuiHands; _hands = state.GuiHands;
Array.Sort(_hands, HandOrderComparer.Instance);
UpdateGui(); UpdateGui();
} }
@@ -97,12 +94,15 @@ namespace Content.Client.Hands
var handName = hand.Name; var handName = hand.Name;
newButton.OnPressed += args => OnHandPressed(args, handName); newButton.OnPressed += args => OnHandPressed(args, handName);
newButton.OnStoragePressed += args => OnStoragePressed(handName); newButton.OnStoragePressed += _ => OnStoragePressed(handName);
newButton.Blocked.Visible = !hand.Enabled;
_itemSlotManager.SetItemSlot(newButton, hand.HeldItem); _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)) if (TryGetActiveHand(out var activeHand))
{ {
activeHand.HandButton.SetActiveHand(true); activeHand.HandButton.SetActiveHand(true);
@@ -114,7 +114,7 @@ namespace Content.Client.Hands
{ {
if (args.Function == EngineKeyFunctions.UIClick) if (args.Function == EngineKeyFunctions.UIClick)
{ {
HandClick?.Invoke(new HandClickEventArgs(handName)); _handsSystem.UIHandClick(_handsComponent, handName);
} }
else if (TryGetHand(handName, out var hand)) else if (TryGetHand(handName, out var hand))
{ {
@@ -124,7 +124,7 @@ namespace Content.Client.Hands
private void OnStoragePressed(string handName) private void OnStoragePressed(string handName)
{ {
HandActivate?.Invoke(new HandActivateEventArgs(handName)); _handsSystem.UIHandActivate(handName);
} }
private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand) private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand)
@@ -145,6 +145,7 @@ namespace Content.Client.Hands
if (hand.Name == handName) if (hand.Name == handName)
foundHand = hand; foundHand = hand;
} }
return foundHand != null; return foundHand != null;
} }
@@ -153,7 +154,9 @@ namespace Content.Client.Hands
base.FrameUpdate(args); base.FrameUpdate(args);
foreach (var hand in _hands) foreach (var hand in _hands)
{
_itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem); _itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem);
}
} }
private HandButton MakeHandButton(HandLocation buttonLocation) private HandButton MakeHandButton(HandLocation buttonLocation)
@@ -173,23 +176,31 @@ namespace Content.Client.Hands
UpdateGui(); 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 var orderX = Map(x.HandLocation);
{ var orderY = Map(y.HandLocation);
public string HandUsed { get; }
public HandActivateEventArgs(string handUsed) return orderX.CompareTo(orderY);
{
HandUsed = handUsed; 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. /// The set of hands to be displayed.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public List<GuiHand> GuiHands { get; } = new(); public GuiHand[] GuiHands { get; }
/// <summary> /// <summary>
/// The name of the currently active hand. /// The name of the currently active hand.
@@ -211,7 +222,7 @@ namespace Content.Client.Hands
[ViewVariables] [ViewVariables]
public string? ActiveHand { get; } public string? ActiveHand { get; }
public HandsGuiState(List<GuiHand> guiHands, string? activeHand = null) public HandsGuiState(GuiHand[] guiHands, string? activeHand = null)
{ {
GuiHands = guiHands; GuiHands = guiHands;
ActiveHand = activeHand; ActiveHand = activeHand;
@@ -247,18 +258,11 @@ namespace Content.Client.Hands
[ViewVariables] [ViewVariables]
public HandButton HandButton { get; set; } = default!; public HandButton HandButton { get; set; } = default!;
/// <summary> public GuiHand(string name, HandLocation handLocation, IEntity? heldItem)
/// 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)
{ {
Name = name; Name = name;
HandLocation = handLocation; HandLocation = handLocation;
HeldItem = heldItem; 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.Client.UserInterface;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -10,5 +11,21 @@ namespace Content.Client.Items.Managers
void UpdateCooldown(ItemSlotButton? cooldownTexture, IEntity? entity); void UpdateCooldown(ItemSlotButton? cooldownTexture, IEntity? entity);
bool SetItemSlot(ItemSlotButton button, IEntity? entity); bool SetItemSlot(ItemSlotButton button, IEntity? entity);
void HoverInSlot(ItemSlotButton button, IEntity? entity, bool fits); 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.Examine;
using Content.Client.Items.UI; using Content.Client.Items.UI;
using Content.Client.Storage; using Content.Client.Storage;
using Content.Client.Verbs; using Content.Client.Verbs;
using Content.Shared.Cooldown; using Content.Shared.Cooldown;
using Content.Shared.Hands.Components;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -28,6 +31,11 @@ namespace Content.Client.Items.Managers
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = 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) public bool SetItemSlot(ItemSlotButton button, IEntity? entity)
{ {
@@ -38,13 +46,26 @@ namespace Content.Client.Items.Managers
} }
else 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; return false;
}
button.ClearHover(); button.ClearHover();
button.SpriteView.Sprite = sprite; button.SpriteView.Sprite = sprite;
button.StorageButton.Visible = entity.HasComponent<ClientStorageComponent>(); button.StorageButton.Visible = entity.HasComponent<ClientStorageComponent>();
} }
button.Entity = entity?.Uid ?? default;
// im lazy
button.UpdateSlotHighlighted();
return true; return true;
} }
@@ -145,5 +166,38 @@ namespace Content.Client.Items.Managers
button.HoverSpriteView.Sprite = hoverSprite; 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 System;
using Content.Client.Cooldown; using Content.Client.Cooldown;
using Content.Client.Items.Managers;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Input; using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Maths; using Robust.Shared.Maths;
namespace Content.Client.Items.UI namespace Content.Client.Items.UI
{ {
public class ItemSlotButton : Control public class ItemSlotButton : Control, IEntityEventSubscriber
{ {
private const string HighlightShader = "SelectionOutlineInrange"; private const string HighlightShader = "SelectionOutlineInrange";
[Dependency] private readonly IItemSlotManager _itemSlotManager = default!;
public EntityUid Entity { get; set; }
public TextureRect Button { get; } public TextureRect Button { get; }
public SpriteView SpriteView { get; } public SpriteView SpriteView { get; }
public SpriteView HoverSpriteView { get; } public SpriteView HoverSpriteView { get; }
@@ -32,6 +38,8 @@ namespace Content.Client.Items.UI
public ItemSlotButton(Texture texture, Texture storageTexture, string textureName) public ItemSlotButton(Texture texture, Texture storageTexture, string textureName)
{ {
IoCManager.InjectDependencies(this);
MinSize = (64, 64); MinSize = (64, 64);
TextureName = textureName; 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() public void ClearHover()
{ {
if (EntityHover) if (EntityHover)

View File

@@ -8,7 +8,9 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using static Content.Client.IoC.StaticIoC; using static Content.Client.IoC.StaticIoC;
@@ -18,6 +20,8 @@ namespace Content.Client.Items.UI
{ {
public class ItemStatusPanel : Control public class ItemStatusPanel : Control
{ {
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] [ViewVariables]
private readonly List<(IItemStatus, Control)> _activeStatusComponents = new(); 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) public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign)
{ {
IoCManager.InjectDependencies(this);
var panel = new StyleBoxTexture var panel = new StyleBoxTexture
{ {
Texture = texture Texture = texture
@@ -117,6 +123,13 @@ namespace Content.Client.Items.UI
return new ItemStatusPanel(ResC.GetTexture(texture), cutOut, flat, textAlign); return new ItemStatusPanel(ResC.GetTexture(texture), cutOut, flat, textAlign);
} }
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateItemName();
}
public void Update(IEntity? entity) public void Update(IEntity? entity)
{ {
if (entity == null) if (entity == null)
@@ -131,12 +144,29 @@ namespace Content.Client.Items.UI
{ {
_entity = entity; _entity = entity;
BuildNewEntityStatus(); BuildNewEntityStatus();
_itemNameLabel.Text = entity.Name;
UpdateItemName();
} }
_panel.Visible = true; _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() private void ClearOldStatus()
{ {
_statusContents.RemoveAllChildren(); _statusContents.RemoveAllChildren();
@@ -162,6 +192,14 @@ namespace Content.Client.Items.UI
_activeStatusComponents.Add((statusComponent, control)); _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 Content.Shared.Kitchen.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Client.Kitchen namespace Content.Client.Kitchen.Components
{ {
[RegisterComponent] [RegisterComponent]
internal sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent 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.Kitchen.Components;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Sound;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.GameObjects;
using Robust.Shared.Log; using Robust.Shared.Log;
namespace Content.Client.Kitchen.Visualizers namespace Content.Client.Kitchen.Visualizers
@@ -17,35 +17,33 @@ namespace Content.Client.Kitchen.Visualizers
base.OnChangeData(component); base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>(); 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)) if (!component.TryGetData(PowerDeviceVisuals.VisualState, out MicrowaveVisualState state))
{ {
state = MicrowaveVisualState.Idle; state = MicrowaveVisualState.Idle;
} }
// The only reason we get the entity system so late is so that tests don't fail... Amazing, huh?
switch (state) switch (state)
{ {
case MicrowaveVisualState.Broken: case MicrowaveVisualState.Broken:
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mwb"); sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mwb");
loopingSoundComponent?.StopAllSounds(); if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StopSoundLoop(microwaveComponent);
break; break;
case MicrowaveVisualState.Idle: case MicrowaveVisualState.Idle:
sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw"); sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw");
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_unlit"); sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_unlit");
loopingSoundComponent?.StopAllSounds(); if(microwaveComponent != null)
EntitySystem.Get<MicrowaveSystem>().StopSoundLoop(microwaveComponent);
break; break;
case MicrowaveVisualState.Cooking: case MicrowaveVisualState.Cooking:
sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw"); sprite.LayerSetState(MicrowaveVisualizerLayers.Base, "mw");
sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_running_unlit"); sprite.LayerSetState(MicrowaveVisualizerLayers.BaseUnlit, "mw_running_unlit");
var audioParams = AudioParams.Default; if(microwaveComponent != null)
audioParams.Loop = true; EntitySystem.Get<MicrowaveSystem>().StartSoundLoop(microwaveComponent);
var scheduledSound = new ScheduledSound();
scheduledSound.Filename = "/Audio/Machines/microwave_loop.ogg";
scheduledSound.AudioParams = audioParams;
loopingSoundComponent?.StopAllSounds();
loopingSoundComponent?.AddScheduledSound(scheduledSound);
break; break;
default: default:

View File

@@ -1,4 +1,5 @@
using Content.Shared.Light.Component; using Content.Shared.Light.Component;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Client.Light.Components namespace Content.Client.Light.Components
@@ -9,6 +10,6 @@ namespace Content.Client.Light.Components
[RegisterComponent] [RegisterComponent]
public class ExpendableLightComponent : SharedExpendableLightComponent 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 Content.Shared.Light.Component;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Client.Light.Visualizers namespace Content.Client.Light.Visualizers
{ {
@@ -17,7 +20,7 @@ namespace Content.Client.Light.Visualizers
return; 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)) 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;
}
}
}

View File

@@ -52,7 +52,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
- type: PointLight - type: PointLight
enabled: false enabled: false
radius: 3 radius: 3
- type: LoopingSound
- type: Appearance - type: Appearance
visuals: visuals:
- type: FlashLightVisualizer - type: FlashLightVisualizer

View File

@@ -17,6 +17,11 @@ namespace Content.Server.Body.Behavior
private float _accumulatedFrameTime; private float _accumulatedFrameTime;
/// <summary>
/// Delay time that determines how often to metabolise blood contents (in seconds).
/// </summary>
private float _updateIntervalSeconds = 1.0f;
/// <summary> /// <summary>
/// Whether the liver is functional. /// Whether the liver is functional.
/// </summary> /// </summary>
@@ -63,13 +68,13 @@ namespace Content.Server.Body.Behavior
_accumulatedFrameTime += frameTime; _accumulatedFrameTime += frameTime;
// Update at most once per second // Update at most once every _updateIntervalSeconds
if (_accumulatedFrameTime < 1) if (_accumulatedFrameTime < _updateIntervalSeconds)
{ {
return; return;
} }
_accumulatedFrameTime -= 1; _accumulatedFrameTime -= _updateIntervalSeconds;
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{ {
@@ -90,6 +95,10 @@ namespace Content.Server.Body.Behavior
continue; continue;
} }
// How much reagent is available to metabolise?
// This needs to be passed to other functions that have metabolism rate information, such that they don't "overmetabolise" a reagent.
var availableReagent = bloodstream.Solution.Solution.GetReagentQuantity(reagent.ReagentId);
//TODO BODY Check if it's a Toxin. If volume < _toxinTolerance, just remove it. If greater, add damage = volume * _toxinLethality //TODO BODY Check if it's a Toxin. If volume < _toxinTolerance, just remove it. If greater, add damage = volume * _toxinLethality
//TODO BODY Check if it has BoozePower > 0. Affect drunkenness, apply damage. Proposed formula (SS13-derived): damage = sqrt(volume) * BoozePower^_alcoholExponent * _alcoholLethality / 10 //TODO BODY Check if it has BoozePower > 0. Affect drunkenness, apply damage. Proposed formula (SS13-derived): damage = sqrt(volume) * BoozePower^_alcoholExponent * _alcoholLethality / 10
//TODO BODY Liver failure. //TODO BODY Liver failure.
@@ -99,8 +108,9 @@ namespace Content.Server.Body.Behavior
// Run metabolism code for each reagent // Run metabolism code for each reagent
foreach (var metabolizable in prototype.Metabolism) foreach (var metabolizable in prototype.Metabolism)
{ {
var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, frameTime); var reagentDelta = metabolizable.Metabolize(Body.Owner, reagent.ReagentId, _updateIntervalSeconds, availableReagent);
bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta); bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
availableReagent -= reagentDelta;
} }
} }
} }

View File

@@ -30,6 +30,8 @@ namespace Content.Server.Body.Behavior
/// </param> /// </param>
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
// Do not metabolise if the organ does not have a body.
if (Body == null) if (Body == null)
{ {
return; return;
@@ -45,7 +47,9 @@ namespace Content.Server.Body.Behavior
_accumulatedFrameTime -= 1; _accumulatedFrameTime -= 1;
if (!Body.Owner.TryGetComponent(out SolutionContainerComponent? solution) || // Note that "Owner" should be the organ that has this behaviour/mechanism, and it should have a dedicated
// solution container. "Body.Owner" is something else, and may have more than one solution container.
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) ||
!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) !Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{ {
return; return;
@@ -61,8 +65,19 @@ namespace Content.Server.Body.Behavior
delta.Increment(1); delta.Increment(1);
if (delta.Lifetime > _digestionDelay) if (delta.Lifetime > _digestionDelay)
{ {
solution.TryRemoveReagent(delta.ReagentId, delta.Quantity); // This reagent has been in the somach long enough, TRY to transfer it.
transferSolution.AddReagent(delta.ReagentId, delta.Quantity); // But first, check if the reagent still exists, and how much is left.
// Some poor spessman may have washed down a potassium snack with some water.
if (solution.Solution.ContainsReagent(delta.ReagentId, out ReagentUnit quantity)){
if (quantity > delta.Quantity) {
quantity = delta.Quantity;
}
solution.TryRemoveReagent(delta.ReagentId, quantity);
transferSolution.AddReagent(delta.ReagentId, quantity);
}
_reagentDeltas.Remove(delta); _reagentDeltas.Remove(delta);
} }
} }
@@ -133,10 +148,10 @@ namespace Content.Server.Body.Behavior
public bool TryTransferSolution(Solution solution) public bool TryTransferSolution(Solution solution)
{ {
if (Body == null || !CanTransferSolution(solution)) if (Owner == null || !CanTransferSolution(solution))
return false; return false;
if (!Body.Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent)) if (!Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent))
{ {
return false; return false;
} }

View File

@@ -1,4 +1,4 @@
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Metabolizable; using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
@@ -9,28 +9,27 @@ namespace Content.Server.Chemistry.Metabolism
{ {
/// <summary> /// <summary>
/// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target, /// Default metabolism for drink reagents. Attempts to find a ThirstComponent on the target,
/// and to update it's thirst values. /// and to update it's thirst values. Inherits metabolisation rate logic from DefaultMetabolizable.
/// </summary> /// </summary>
[DataDefinition] [DataDefinition]
public class DefaultDrink : IMetabolizable public class DefaultDrink : DefaultMetabolizable
{ {
//Rate of metabolism in units / second
[DataField("rate")]
public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
//How much thirst is satiated when 1u of the reagent is metabolized //How much thirst is satiated when 1u of the reagent is metabolized
[DataField("hydrationFactor")] [DataField("hydrationFactor")]
public float HydrationFactor { get; set; } = 30.0f; public float HydrationFactor { get; set; } = 30.0f;
//Remove reagent at set rate, satiate thirst if a ThirstComponent can be found //Remove reagent at set rate, satiate thirst if a ThirstComponent can be found
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime) public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{ {
var metabolismAmount = MetabolismRate * tickTime; // use DefaultMetabolism to determine how much reagent we should metabolize
var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
// If metabolizing entity has a ThirstComponent, hydrate them.
if (solutionEntity.TryGetComponent(out ThirstComponent? thirst)) if (solutionEntity.TryGetComponent(out ThirstComponent? thirst))
thirst.UpdateThirst(metabolismAmount.Float() * HydrationFactor); thirst.UpdateThirst(amountMetabolized.Float() * HydrationFactor);
//Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence //Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
return metabolismAmount; return amountMetabolized;
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Metabolizable; using Content.Shared.Chemistry.Metabolizable;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
@@ -9,30 +9,30 @@ namespace Content.Server.Chemistry.Metabolism
{ {
/// <summary> /// <summary>
/// Default metabolism for food reagents. Attempts to find a HungerComponent on the target, /// Default metabolism for food reagents. Attempts to find a HungerComponent on the target,
/// and to update it's hunger values. /// and to update it's hunger values. Inherits metabolisation rate logic from DefaultMetabolizable.
/// </summary> /// </summary>
[DataDefinition] [DataDefinition]
public class DefaultFood : IMetabolizable public class DefaultFood : DefaultMetabolizable
{ {
/// <summary>
/// Rate of metabolism in units / second
/// </summary>
[DataField("rate")] public ReagentUnit MetabolismRate { get; private set; } = ReagentUnit.New(1.0);
/// <summary> /// <summary>
/// How much hunger is satiated when 1u of the reagent is metabolized /// How much hunger is satiated when 1u of the reagent is metabolized
/// </summary> /// </summary>
[DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f; [DataField("nutritionFactor")] public float NutritionFactor { get; set; } = 30.0f;
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
{
var metabolismAmount = MetabolismRate * tickTime;
if (solutionEntity.TryGetComponent(out HungerComponent? hunger))
hunger.UpdateFood(metabolismAmount.Float() * NutritionFactor);
//Return amount of reagent to be removed, remove reagent regardless of HungerComponent presence //Remove reagent at set rate, satiate hunger if a HungerComponent can be found
return metabolismAmount; public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{
// use DefaultMetabolism to determine how much reagent we should metabolize
var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
// If metabolizing entity has a HungerComponent, feed them.
if (solutionEntity.TryGetComponent(out HungerComponent? hunger))
hunger.UpdateFood(amountMetabolized.Float() * NutritionFactor);
//Return amount of reagent to be removed. Reagent is removed regardless of HungerComponent presence
return amountMetabolized;
} }
} }
} }

View File

@@ -10,16 +10,11 @@ namespace Content.Server.Chemistry.Metabolism
{ {
/// <summary> /// <summary>
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target, /// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
/// and to update its damage values. /// and to update its damage values. Inherits metabolisation rate logic from DefaultMetabolizable.
/// </summary> /// </summary>
[DataDefinition] [DataDefinition]
public class HealthChangeMetabolism : IMetabolizable public class HealthChangeMetabolism : DefaultMetabolizable
{ {
/// <summary>
/// How much of the reagent should be metabolized each sec.
/// </summary>
[DataField("rate")]
public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
/// <summary> /// <summary>
/// How much damage is changed when 1u of the reagent is metabolized. /// How much damage is changed when 1u of the reagent is metabolized.
@@ -41,14 +36,23 @@ namespace Content.Server.Chemistry.Metabolism
/// <param name="solutionEntity"></param> /// <param name="solutionEntity"></param>
/// <param name="reagentId"></param> /// <param name="reagentId"></param>
/// <param name="tickTime"></param> /// <param name="tickTime"></param>
/// <param name="availableReagent">Reagent available to be metabolized.</param>
/// <returns></returns> /// <returns></returns>
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime) public override ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{ {
// use DefaultMetabolism to determine how much reagent we should metabolize
var amountMetabolized = base.Metabolize(solutionEntity, reagentId, tickTime, availableReagent);
// how much does this much reagent heal for
var healthChangeAmount = HealthChange * amountMetabolized.Float();
if (solutionEntity.TryGetComponent(out IDamageableComponent? health)) if (solutionEntity.TryGetComponent(out IDamageableComponent? health))
{ {
health.ChangeDamage(DamageType, (int)HealthChange, true); // Heal damage by healthChangeAmmount, rounding down to nearest integer
float decHealthChange = (float) (HealthChange - (int) HealthChange); health.ChangeDamage(DamageType, (int) healthChangeAmount, true);
_accumulatedHealth += decHealthChange;
// Store decimal remainder of healthChangeAmmount in _accumulatedHealth
_accumulatedHealth += (healthChangeAmount - (int) healthChangeAmount);
if (_accumulatedHealth >= 1) if (_accumulatedHealth >= 1)
{ {
@@ -62,7 +66,7 @@ namespace Content.Server.Chemistry.Metabolism
_accumulatedHealth += 1; _accumulatedHealth += 1;
} }
} }
return MetabolismRate; return amountMetabolized;
} }
} }
} }

View File

@@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Chemistry.ReagentEntityReactions namespace Content.Server.Chemistry.ReagentEntityReactions
{ {
[UsedImplicitly] [UsedImplicitly]
[DataDefinition]
public class AddToSolutionReaction : ReagentEntityReaction public class AddToSolutionReaction : ReagentEntityReaction
{ {
[DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))] [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))]

View File

@@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Chemistry.ReagentEntityReactions namespace Content.Server.Chemistry.ReagentEntityReactions
{ {
[UsedImplicitly] [UsedImplicitly]
[DataDefinition]
public class ExtinguishReaction : ReagentEntityReaction public class ExtinguishReaction : ReagentEntityReaction
{ {
[DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))] [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))]

View File

@@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Chemistry.ReagentEntityReactions namespace Content.Server.Chemistry.ReagentEntityReactions
{ {
[UsedImplicitly] [UsedImplicitly]
[DataDefinition]
public class FlammableReaction : ReagentEntityReaction public class FlammableReaction : ReagentEntityReaction
{ {
[DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))] [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))]

View File

@@ -10,7 +10,6 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Chemistry.ReagentEntityReactions namespace Content.Server.Chemistry.ReagentEntityReactions
{ {
[UsedImplicitly] [UsedImplicitly]
[DataDefinition]
public class WashCreamPieReaction : ReagentEntityReaction public class WashCreamPieReaction : ReagentEntityReaction
{ {
[DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))] [DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))]

View File

@@ -2,6 +2,7 @@ using Content.Shared.ActionBlocker;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components; using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.DragDrop; using Content.Shared.DragDrop;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Verbs; using Content.Shared.Verbs;

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
@@ -10,7 +11,6 @@ using Content.Shared.Audio;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Physics.Pull;
using Content.Shared.Pulling.Components; using Content.Shared.Pulling.Components;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -20,9 +20,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Hands.Components namespace Content.Server.Hands.Components
@@ -39,48 +37,6 @@ namespace Content.Server.Hands.Components
int IDisarmedAct.Priority => int.MaxValue; // We want this to be the last disarm act to run. int IDisarmedAct.Priority => int.MaxValue; // We want this to be the last disarm act to run.
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case PullAttemptMessage msg:
AttemptPull(msg);
break;
case PullStartedMessage:
StartPulling();
break;
case PullStoppedMessage:
StopPulling();
break;
}
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case ClientChangedHandMsg msg:
ActiveHand = msg.HandName;
break;
case ClientAttackByInHandMsg msg:
InteractHandWithActiveHand(msg.HandName);
break;
case UseInHandMsg:
UseActiveHeldEntity();
break;
case ActivateInHandMsg msg:
ActivateHeldEntity(msg.HandName);
break;
case MoveItemFromHandMsg msg:
TryMoveHeldEntityToActiveHand(msg.HandName);
break;
}
}
protected override void OnHeldEntityRemovedFromHand(IEntity heldEntity, HandState handState) protected override void OnHeldEntityRemovedFromHand(IEntity heldEntity, HandState handState)
{ {
if (heldEntity.TryGetComponent(out ItemComponent? item)) if (heldEntity.TryGetComponent(out ItemComponent? item))
@@ -145,7 +101,8 @@ namespace Content.Server.Hands.Components
if (pickupDirection == initialPosition.ToMapPos(Owner.EntityManager)) if (pickupDirection == initialPosition.ToMapPos(Owner.EntityManager))
return; return;
SendNetworkMessage(new PickupAnimationMessage(entity.Uid, pickupDirection, initialPosition)); Owner.EntityManager.EntityNetManager!.SendSystemNetworkMessage(
new PickupAnimationMessage(entity.Uid, pickupDirection, initialPosition));
} }
#region Pull/Disarm #region Pull/Disarm
@@ -155,9 +112,17 @@ namespace Content.Server.Hands.Components
if (args.Part.PartType != BodyPartType.Hand) if (args.Part.PartType != BodyPartType.Hand)
return; return;
var handLocation = ReadOnlyHands.Count == 0 ? HandLocation.Right : HandLocation.Left; //TODO: make hand body part have a handlocation? // If this annoys you, which it should.
// Ping Smugleaf.
var location = args.Part.Symmetry switch
{
BodyPartSymmetry.None => HandLocation.Middle,
BodyPartSymmetry.Left => HandLocation.Left,
BodyPartSymmetry.Right => HandLocation.Right,
_ => throw new ArgumentOutOfRangeException()
};
AddHand(args.Slot, handLocation); AddHand(args.Slot, location);
} }
void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs args) void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs args)
@@ -209,41 +174,13 @@ namespace Content.Server.Hands.Components
return pullable.TryStopPull(); return pullable.TryStopPull();
} }
private void AttemptPull(PullAttemptMessage msg)
{
if (!ReadOnlyHands.Any(hand => hand.Enabled))
{
msg.Cancelled = true;
}
}
private void StartPulling()
{
var firstFreeHand = Hands.FirstOrDefault(hand => hand.Enabled);
if (firstFreeHand == null)
return;
DisableHand(firstFreeHand);
}
private void StopPulling()
{
var firstOccupiedHand = Hands.FirstOrDefault(hand => !hand.Enabled);
if (firstOccupiedHand == null)
return;
EnableHand(firstOccupiedHand);
}
#endregion #endregion
#region Old public methods #region Old public methods
public IEnumerable<string> HandNames => ReadOnlyHands.Select(h => h.Name); public IEnumerable<string> HandNames => Hands.Select(h => h.Name);
public int Count => ReadOnlyHands.Count; public int Count => Hands.Count;
/// <summary> /// <summary>
/// Returns a list of all hand names, with the active hand being first. /// Returns a list of all hand names, with the active hand being first.
@@ -253,9 +190,9 @@ namespace Content.Server.Hands.Components
if (ActiveHand != null) if (ActiveHand != null)
yield return ActiveHand; yield return ActiveHand;
foreach (var hand in ReadOnlyHands) foreach (var hand in Hands)
{ {
if (hand.Name == ActiveHand || !hand.Enabled) if (hand.Name == ActiveHand)
continue; continue;
yield return hand.Name; yield return hand.Name;

View File

@@ -0,0 +1,57 @@
using Content.Server.Pulling;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Hands
{
[UsedImplicitly]
public sealed class HandVirtualPullSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<HandVirtualPullComponent, DroppedEvent>(HandlePullerDropped);
SubscribeLocalEvent<HandVirtualPullComponent, UnequippedHandEvent>(HandlePullerUnequipped);
SubscribeLocalEvent<HandVirtualPullComponent, BeforeInteractEvent>(HandleBeforeInteract);
}
private static void HandleBeforeInteract(
EntityUid uid,
HandVirtualPullComponent component,
BeforeInteractEvent args)
{
// No interactions with a virtual pull, please.
args.Handled = true;
}
// If the virtual pull gets removed from the hands for any reason, cancel the pull and delete it.
private void HandlePullerUnequipped(EntityUid uid, HandVirtualPullComponent component, UnequippedHandEvent args)
{
MaybeDelete(component, args.User);
}
private void HandlePullerDropped(EntityUid uid, HandVirtualPullComponent component, DroppedEvent args)
{
MaybeDelete(component, args.User);
}
private void MaybeDelete(HandVirtualPullComponent comp, IEntity? user)
{
var pulled = comp.PulledEntity;
if (!ComponentManager.TryGetComponent(pulled, out PullableComponent? pullable))
return;
if (pullable.Puller != user)
return;
pullable.TryStopPull(user);
comp.Owner.QueueDelete();
}
}
}

View File

@@ -13,9 +13,9 @@ using Content.Shared.Hands;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Physics.Pull;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -23,6 +23,7 @@ using Robust.Shared.Localization;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Players; using Robust.Shared.Players;
using Robust.Shared.Utility;
using static Content.Shared.Inventory.EquipmentSlotDefines; using static Content.Shared.Inventory.EquipmentSlotDefines;
namespace Content.Server.Hands namespace Content.Server.Hands
@@ -38,15 +39,126 @@ namespace Content.Server.Hands
base.Initialize(); base.Initialize();
SubscribeLocalEvent<HandsComponent, ExaminedEvent>(HandleExamined); SubscribeLocalEvent<HandsComponent, ExaminedEvent>(HandleExamined);
SubscribeNetworkEvent<ActivateInHandMsg>(HandleActivateInHand);
SubscribeNetworkEvent<ClientInteractUsingInHandMsg>(HandleInteractUsingInHand);
SubscribeNetworkEvent<UseInHandMsg>(HandleUseInHand);
SubscribeNetworkEvent<MoveItemFromHandMsg>(HandleMoveItemFromHand);
SubscribeLocalEvent<HandsComponent, PullAttemptMessage>(HandlePullAttempt);
SubscribeLocalEvent<HandsComponent, PullStartedMessage>(HandlePullStarted);
SubscribeLocalEvent<HandsComponent, PullStoppedMessage>(HandlePullStopped);
CommandBinds.Builder CommandBinds.Builder
.Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem)) .Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem))
.Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem))
.Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)) .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack))
.Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)) .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt))
.Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false))
.Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed))
.Register<HandsSystem>(); .Register<HandsSystem>();
} }
private static void HandlePullAttempt(EntityUid uid, HandsComponent component, PullAttemptMessage args)
{
// Cancel pull if all hands full.
if (component.Hands.All(hand => !hand.IsEmpty))
args.Cancelled = true;
}
private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args)
{
foreach (var handName in component.ActivePriorityEnumerable())
{
var hand = component.GetHand(handName);
if (!hand.IsEmpty)
continue;
var pos = component.Owner.Transform.Coordinates;
var virtualPull = EntityManager.SpawnEntity("HandVirtualPull", pos);
var virtualPullComp = virtualPull.GetComponent<HandVirtualPullComponent>();
virtualPullComp.PulledEntity = args.Pulled.Owner.Uid;
component.PutEntityIntoHand(hand, virtualPull);
return;
}
DebugTools.Assert("Unable to find available hand when starting pulling??");
}
private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args)
{
// Try find hand that is doing this pull.
// and clear it.
foreach (var hand in component.Hands)
{
if (hand.HeldEntity == null
|| !hand.HeldEntity.TryGetComponent(out HandVirtualPullComponent? virtualPull)
|| virtualPull.PulledEntity != args.Pulled.Owner.Uid)
continue;
hand.HeldEntity.Delete();
break;
}
}
private void SwapHandsPressed(ICommonSession? session)
{
var player = session?.AttachedEntity;
if (player == null)
return;
if (!player.TryGetComponent(out SharedHandsComponent? hands))
return;
if (!hands.TryGetSwapHandsResult(out var nextHand))
return;
hands.ActiveHand = nextHand;
}
private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
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;
hands.TryDropHand(activeHand, coords);
return false;
}
private void HandleMoveItemFromHand(MoveItemFromHandMsg msg, EntitySessionEventArgs args)
{
if (!TryGetHandsComp(args.SenderSession, out var hands))
return;
hands.TryMoveHeldEntityToActiveHand(msg.HandName);
}
private void HandleUseInHand(UseInHandMsg msg, EntitySessionEventArgs args)
{
if (!TryGetHandsComp(args.SenderSession, out var hands))
return;
hands.UseActiveHeldEntity();
}
private void HandleInteractUsingInHand(ClientInteractUsingInHandMsg msg, EntitySessionEventArgs args)
{
if (!TryGetHandsComp(args.SenderSession, out var hands))
return;
hands.InteractHandWithActiveHand(msg.HandName);
}
public override void Shutdown() public override void Shutdown()
{ {
base.Shutdown(); base.Shutdown();
@@ -54,6 +166,14 @@ namespace Content.Server.Hands
CommandBinds.Unregister<HandsSystem>(); CommandBinds.Unregister<HandsSystem>();
} }
private void HandleActivateInHand(ActivateInHandMsg msg, EntitySessionEventArgs args)
{
if (!TryGetHandsComp(args.SenderSession, out var hands))
return;
hands.ActivateHeldEntity(msg.HandName);
}
protected override void DropAllItemsInHands(IEntity entity, bool doMobChecks = true) protected override void DropAllItemsInHands(IEntity entity, bool doMobChecks = true)
{ {
base.DropAllItemsInHands(entity, doMobChecks); base.DropAllItemsInHands(entity, doMobChecks);
@@ -75,25 +195,21 @@ namespace Content.Server.Hands
} }
} }
protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) private static bool TryGetHandsComp(
{ ICommonSession? session,
component.Dirty(); [NotNullWhen(true)] out SharedHandsComponent? hands)
}
private bool TryGetHandsComp(ICommonSession? session, [NotNullWhen(true)] out SharedHandsComponent? hands)
{ {
hands = default; hands = default;
if (session is not IPlayerSession playerSession) if (session is not IPlayerSession playerSession)
return false; return false;
var playerEnt = playerSession?.AttachedEntity; var playerEnt = playerSession.AttachedEntity;
if (playerEnt == null || !playerEnt.IsValid()) if (playerEnt == null || !playerEnt.IsValid())
return false; return false;
playerEnt.TryGetComponent(out hands); return playerEnt.TryGetComponent(out hands);
return hands != null;
} }
private void HandleActivateItem(ICommonSession? session) private void HandleActivateItem(ICommonSession? session)

View File

@@ -387,6 +387,18 @@ namespace Content.Server.Interaction
return false; return false;
} }
private async Task<bool> InteractDoBefore(
IEntity user,
IEntity used,
IEntity? target,
EntityCoordinates clickLocation,
bool canReach)
{
var ev = new BeforeInteractEvent(user, used, target, clickLocation, canReach);
RaiseLocalEvent(used.Uid, ev, false);
return ev.Handled;
}
/// <summary> /// <summary>
/// Uses a item/object on an entity /// Uses a item/object on an entity
/// Finds components with the InteractUsing interface and calls their function /// Finds components with the InteractUsing interface and calls their function
@@ -397,6 +409,9 @@ namespace Content.Server.Interaction
if (!_actionBlockerSystem.CanInteract(user)) if (!_actionBlockerSystem.CanInteract(user))
return; return;
if (await InteractDoBefore(user, used, target, clickLocation, true))
return;
// all interactions should only happen when in range / unobstructed, so no range check is needed // all interactions should only happen when in range / unobstructed, so no range check is needed
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation); var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
RaiseLocalEvent(target.Uid, interactUsingEvent); RaiseLocalEvent(target.Uid, interactUsingEvent);
@@ -696,6 +711,9 @@ namespace Content.Server.Interaction
/// </summary> /// </summary>
public async Task<bool> InteractUsingRanged(IEntity user, IEntity used, IEntity? target, EntityCoordinates clickLocation, bool inRangeUnobstructed) public async Task<bool> InteractUsingRanged(IEntity user, IEntity used, IEntity? target, EntityCoordinates clickLocation, bool inRangeUnobstructed)
{ {
if (await InteractDoBefore(user, used, inRangeUnobstructed ? target : null, clickLocation, false))
return true;
if (target != null) if (target != null)
{ {
var rangedMsg = new RangedInteractEvent(user, used, target, clickLocation); var rangedMsg = new RangedInteractEvent(user, used, target, clickLocation);
@@ -715,10 +733,7 @@ namespace Content.Server.Interaction
} }
} }
if (inRangeUnobstructed) return await InteractDoAfter(user, used, inRangeUnobstructed ? target : null, clickLocation, false);
return await InteractDoAfter(user, used, target, clickLocation, false);
else
return await InteractDoAfter(user, used, null, clickLocation, false);
} }
public void DoAttack(IEntity user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default) public void DoAttack(IEntity user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default)

View File

@@ -195,6 +195,9 @@ namespace Content.Server.Kitchen.EntitySystems
while (_uiUpdateQueue.TryDequeue(out var comp)) while (_uiUpdateQueue.TryDequeue(out var comp))
{ {
if(comp.Deleted)
continue;
bool canJuice = false; bool canJuice = false;
bool canGrind = false; bool canGrind = false;
if (comp.BeakerContainer.ContainedEntity != null) if (comp.BeakerContainer.ContainedEntity != null)

View File

@@ -20,8 +20,6 @@ namespace Content.Server.Light.Components
[RegisterComponent] [RegisterComponent]
public class ExpendableLightComponent : SharedExpendableLightComponent, IUse public class ExpendableLightComponent : SharedExpendableLightComponent, IUse
{ {
private static readonly AudioParams LoopedSoundParams = new(0, 1, "Master", 62.5f, 1, true, 0.3f);
/// <summary> /// <summary>
/// Status of light, whether or not it is emitting light. /// Status of light, whether or not it is emitting light.
/// </summary> /// </summary>
@@ -77,18 +75,20 @@ namespace Content.Server.Light.Components
private void UpdateVisualizer() private void UpdateVisualizer()
{ {
_appearance?.SetData(ExpendableLightVisuals.State, CurrentState);
switch (CurrentState) switch (CurrentState)
{ {
case ExpendableLightState.Lit: case ExpendableLightState.Lit:
_appearance?.SetData(ExpendableLightVisuals.State, TurnOnBehaviourID); _appearance?.SetData(ExpendableLightVisuals.Behavior, TurnOnBehaviourID);
break; break;
case ExpendableLightState.Fading: case ExpendableLightState.Fading:
_appearance?.SetData(ExpendableLightVisuals.State, FadeOutBehaviourID); _appearance?.SetData(ExpendableLightVisuals.Behavior, FadeOutBehaviourID);
break; break;
case ExpendableLightState.Dead: case ExpendableLightState.Dead:
_appearance?.SetData(ExpendableLightVisuals.State, string.Empty); _appearance?.SetData(ExpendableLightVisuals.Behavior, string.Empty);
break; break;
} }
} }
@@ -100,12 +100,6 @@ namespace Content.Server.Light.Components
switch (CurrentState) switch (CurrentState)
{ {
case ExpendableLightState.Lit: case ExpendableLightState.Lit:
if (LoopedSound != string.Empty && Owner.TryGetComponent<LoopingLoopingSoundComponent>(out var loopingSound))
{
loopingSound.Play(LoopedSound, LoopedSoundParams);
}
if (LitSound.TryGetSound(out var litSound)) if (LitSound.TryGetSound(out var litSound))
{ {
SoundSystem.Play(Filter.Pvs(Owner), litSound, Owner); SoundSystem.Play(Filter.Pvs(Owner), litSound, Owner);
@@ -131,11 +125,6 @@ namespace Content.Server.Light.Components
SoundSystem.Play(Filter.Pvs(Owner), dieSound, Owner); SoundSystem.Play(Filter.Pvs(Owner), dieSound, Owner);
} }
if (LoopedSound != string.Empty && Owner.TryGetComponent<LoopingLoopingSoundComponent>(out var loopSound))
{
loopSound.StopAllSounds();
}
sprite.LayerSetState(0, IconStateSpent); sprite.LayerSetState(0, IconStateSpent);
sprite.LayerSetShader(0, "shaded"); sprite.LayerSetShader(0, "shaded");
sprite.LayerSetVisible(1, false); sprite.LayerSetVisible(1, false);

View File

@@ -19,7 +19,6 @@ namespace Content.Server.Power.EntitySystems
private readonly HashSet<PowerNet> _powerNetReconnectQueue = new(); private readonly HashSet<PowerNet> _powerNetReconnectQueue = new();
private readonly HashSet<ApcNet> _apcNetReconnectQueue = new(); private readonly HashSet<ApcNet> _apcNetReconnectQueue = new();
private int _nextId = 1;
private readonly BatteryRampPegSolver _solver = new(); private readonly BatteryRampPegSolver _solver = new();
public override void Initialize() public override void Initialize()
@@ -50,7 +49,7 @@ namespace Content.Server.Power.EntitySystems
private void ApcPowerReceiverShutdown(EntityUid uid, ApcPowerReceiverComponent component, private void ApcPowerReceiverShutdown(EntityUid uid, ApcPowerReceiverComponent component,
ComponentShutdown args) ComponentShutdown args)
{ {
_powerState.Loads.Remove(component.NetworkLoad.Id); _powerState.Loads.Free(component.NetworkLoad.Id);
} }
private static void ApcPowerReceiverPaused( private static void ApcPowerReceiverPaused(
@@ -68,7 +67,7 @@ namespace Content.Server.Power.EntitySystems
private void BatteryShutdown(EntityUid uid, PowerNetworkBatteryComponent component, ComponentShutdown args) private void BatteryShutdown(EntityUid uid, PowerNetworkBatteryComponent component, ComponentShutdown args)
{ {
_powerState.Batteries.Remove(component.NetworkBattery.Id); _powerState.Batteries.Free(component.NetworkBattery.Id);
} }
private static void BatteryPaused(EntityUid uid, PowerNetworkBatteryComponent component, EntityPausedEvent args) private static void BatteryPaused(EntityUid uid, PowerNetworkBatteryComponent component, EntityPausedEvent args)
@@ -83,7 +82,7 @@ namespace Content.Server.Power.EntitySystems
private void PowerConsumerShutdown(EntityUid uid, PowerConsumerComponent component, ComponentShutdown args) private void PowerConsumerShutdown(EntityUid uid, PowerConsumerComponent component, ComponentShutdown args)
{ {
_powerState.Loads.Remove(component.NetworkLoad.Id); _powerState.Loads.Free(component.NetworkLoad.Id);
} }
private static void PowerConsumerPaused(EntityUid uid, PowerConsumerComponent component, EntityPausedEvent args) private static void PowerConsumerPaused(EntityUid uid, PowerConsumerComponent component, EntityPausedEvent args)
@@ -98,7 +97,7 @@ namespace Content.Server.Power.EntitySystems
private void PowerSupplierShutdown(EntityUid uid, PowerSupplierComponent component, ComponentShutdown args) private void PowerSupplierShutdown(EntityUid uid, PowerSupplierComponent component, ComponentShutdown args)
{ {
_powerState.Supplies.Remove(component.NetworkSupply.Id); _powerState.Supplies.Free(component.NetworkSupply.Id);
} }
private static void PowerSupplierPaused(EntityUid uid, PowerSupplierComponent component, EntityPausedEvent args) private static void PowerSupplierPaused(EntityUid uid, PowerSupplierComponent component, EntityPausedEvent args)
@@ -113,7 +112,7 @@ namespace Content.Server.Power.EntitySystems
public void DestroyPowerNet(PowerNet powerNet) public void DestroyPowerNet(PowerNet powerNet)
{ {
_powerState.Networks.Remove(powerNet.NetworkNode.Id); _powerState.Networks.Free(powerNet.NetworkNode.Id);
} }
public void QueueReconnectPowerNet(PowerNet powerNet) public void QueueReconnectPowerNet(PowerNet powerNet)
@@ -128,7 +127,7 @@ namespace Content.Server.Power.EntitySystems
public void DestroyApcNet(ApcNet apcNet) public void DestroyApcNet(ApcNet apcNet)
{ {
_powerState.Networks.Remove(apcNet.NetworkNode.Id); _powerState.Networks.Free(apcNet.NetworkNode.Id);
} }
public void QueueReconnectApcNet(ApcNet apcNet) public void QueueReconnectApcNet(ApcNet apcNet)
@@ -213,26 +212,22 @@ namespace Content.Server.Power.EntitySystems
private void AllocLoad(PowerState.Load load) private void AllocLoad(PowerState.Load load)
{ {
load.Id = AllocId(); _powerState.Loads.Allocate(out load.Id) = load;
_powerState.Loads.Add(load.Id, load);
} }
private void AllocSupply(PowerState.Supply supply) private void AllocSupply(PowerState.Supply supply)
{ {
supply.Id = AllocId(); _powerState.Supplies.Allocate(out supply.Id) = supply;
_powerState.Supplies.Add(supply.Id, supply);
} }
private void AllocBattery(PowerState.Battery battery) private void AllocBattery(PowerState.Battery battery)
{ {
battery.Id = AllocId(); _powerState.Batteries.Allocate(out battery.Id) = battery;
_powerState.Batteries.Add(battery.Id, battery);
} }
private void AllocNetwork(PowerState.Network network) private void AllocNetwork(PowerState.Network network)
{ {
network.Id = AllocId(); _powerState.Networks.Allocate(out network.Id) = network;
_powerState.Networks.Add(network.Id, network);
} }
private static void DoReconnectApcNet(ApcNet net) private static void DoReconnectApcNet(ApcNet net)
@@ -296,11 +291,6 @@ namespace Content.Server.Power.EntitySystems
battery.NetworkBattery.LinkedNetworkDischarging = netNode.Id; battery.NetworkBattery.LinkedNetworkDischarging = netNode.Id;
} }
} }
private PowerState.NodeId AllocId()
{
return new(_nextId++);
}
} }
/// <summary> /// <summary>

View File

@@ -1,181 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static Content.Server.Power.Pow3r.PowerState;
namespace Content.Server.Power.Pow3r
{
/// <summary>
/// Partial implementation of full-graph-walking power solving under pow3r.
/// Concept described at https://hackmd.io/@ss14/lowpower
/// </summary>
/// <remarks>
/// Many features like batteries, cycle detection, join handling, etc... are not implemented at all.
/// Seriously, this implementation barely works. Ah well.
/// <see cref="BatteryRampPegSolver"/> is better.
/// </remarks>
public class GraphWalkSolver : IPowerSolver
{
public void Tick(float frameTime, PowerState state)
{
foreach (var load in state.Loads.Values)
{
load.ReceivingPower = 0;
}
foreach (var supply in state.Supplies.Values)
{
supply.CurrentSupply = 0;
}
foreach (var network in state.Networks.Values)
{
// Clear some stuff.
network.LocalDemandMet = 0;
// Add up demands in network.
network.LocalDemandTotal = network.Loads
.Select(l => state.Loads[l])
.Where(c => c.Enabled)
.Sum(c => c.DesiredPower);
// Add up supplies in network.
var availableSupplySum = 0f;
var maxSupplySum = 0f;
foreach (var supplyId in network.Supplies)
{
var supply = state.Supplies[supplyId];
if (!supply.Enabled)
continue;
var rampMax = supply.SupplyRampPosition + supply.SupplyRampTolerance;
var effectiveSupply = Math.Min(rampMax, supply.MaxSupply);
supply.EffectiveMaxSupply = effectiveSupply;
availableSupplySum += effectiveSupply;
maxSupplySum += supply.MaxSupply;
}
network.AvailableSupplyTotal = availableSupplySum;
network.TheoreticalSupplyTotal = maxSupplySum;
}
// Sort networks by tree height so that suppliers that have less possible loads go FIRST.
// Idea being that a backup generator on a small subnet should do more work
// so that a larger generator that covers more networks can put its power elsewhere.
var sortedByHeight = state.Networks.Values.OrderBy(v => TotalSubLoadCount(state, v)).ToArray();
// Go over every network with supply to send power.
foreach (var network in sortedByHeight)
{
// Find all loads recursively, and sum them up.
var subNets = new List<Network>();
var totalDemand = 0f;
GetLoadingNetworksRecursively(state, network, subNets, ref totalDemand);
if (totalDemand == 0)
continue;
// Calculate power delivered.
var power = Math.Min(totalDemand, network.AvailableSupplyTotal);
// Distribute load across supplies in network.
foreach (var supplyId in network.Supplies)
{
var supply = state.Supplies[supplyId];
if (!supply.Enabled)
continue;
if (supply.EffectiveMaxSupply != 0)
{
var ratio = supply.EffectiveMaxSupply / network.AvailableSupplyTotal;
supply.CurrentSupply = ratio * power;
}
else
{
supply.CurrentSupply = 0;
}
if (supply.MaxSupply != 0)
{
var ratio = supply.MaxSupply / network.TheoreticalSupplyTotal;
supply.SupplyRampTarget = ratio * totalDemand;
}
else
{
supply.SupplyRampTarget = 0;
}
}
// Distribute supply across subnet loads.
foreach (var subNet in subNets)
{
var rem = subNet.RemainingDemand;
var ratio = rem / totalDemand;
subNet.LocalDemandMet += ratio * power;
}
}
// Distribute power across loads in networks.
foreach (var network in state.Networks.Values)
{
if (network.LocalDemandMet == 0)
continue;
foreach (var loadId in network.Loads)
{
var load = state.Loads[loadId];
if (!load.Enabled)
continue;
var ratio = load.DesiredPower / network.LocalDemandTotal;
load.ReceivingPower = ratio * network.LocalDemandMet;
}
}
PowerSolverShared.UpdateRampPositions(frameTime, state);
}
private int TotalSubLoadCount(PowerState state, Network network)
{
// TODO: Cycle detection.
var height = network.Loads.Count;
foreach (var batteryId in network.BatteriesCharging)
{
var battery = state.Batteries[batteryId];
if (battery.LinkedNetworkDischarging != default)
{
height += TotalSubLoadCount(state, state.Networks[battery.LinkedNetworkDischarging]);
}
}
return height;
}
private void GetLoadingNetworksRecursively(
PowerState state,
Network network,
List<Network> networks,
ref float totalDemand)
{
networks.Add(network);
totalDemand += network.LocalDemandTotal - network.LocalDemandMet;
foreach (var batteryId in network.BatteriesCharging)
{
var battery = state.Batteries[batteryId];
if (battery.LinkedNetworkDischarging != default)
{
GetLoadingNetworksRecursively(
state,
state.Networks[battery.LinkedNetworkDischarging],
networks,
ref totalDemand);
}
}
}
}
}

View File

@@ -1,40 +1,51 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Pow3r namespace Content.Server.Power.Pow3r
{ {
public sealed class PowerState public sealed class PowerState
{ {
public const int MaxTickData = 180;
public static readonly JsonSerializerOptions SerializerOptions = new() public static readonly JsonSerializerOptions SerializerOptions = new()
{ {
IncludeFields = true, IncludeFields = true,
Converters = {new NodeIdJsonConverter()} Converters = {new NodeIdJsonConverter()}
}; };
public Dictionary<NodeId, Supply> Supplies = new(); public GenIdStorage<Supply> Supplies = new();
public Dictionary<NodeId, Network> Networks = new(); public GenIdStorage<Network> Networks = new();
public Dictionary<NodeId, Load> Loads = new(); public GenIdStorage<Load> Loads = new();
public Dictionary<NodeId, Battery> Batteries = new(); public GenIdStorage<Battery> Batteries = new();
public readonly struct NodeId : IEquatable<NodeId> public readonly struct NodeId : IEquatable<NodeId>
{ {
public readonly int Id; public readonly int Index;
public readonly int Generation;
public NodeId(int id) public long Combined => (uint) Index | ((long) Generation << 32);
public NodeId(int index, int generation)
{ {
Id = id; Index = index;
Generation = generation;
}
public NodeId(long combined)
{
Index = (int) combined;
Generation = (int) (combined >> 32);
} }
public bool Equals(NodeId other) public bool Equals(NodeId other)
{ {
return Id == other.Id; return Index == other.Index && Generation == other.Generation;
} }
public override bool Equals(object? obj) public override bool Equals(object? obj)
@@ -44,7 +55,7 @@ namespace Content.Server.Power.Pow3r
public override int GetHashCode() public override int GetHashCode()
{ {
return Id; return HashCode.Combine(Index, Generation);
} }
public static bool operator ==(NodeId left, NodeId right) public static bool operator ==(NodeId left, NodeId right)
@@ -59,7 +70,261 @@ namespace Content.Server.Power.Pow3r
public override string ToString() public override string ToString()
{ {
return Id.ToString(); return $"{Index} (G{Generation})";
}
}
public static class GenIdStorage
{
public static GenIdStorage<T> FromEnumerable<T>(IEnumerable<(NodeId, T)> enumerable)
{
return GenIdStorage<T>.FromEnumerable(enumerable);
}
}
public sealed class GenIdStorage<T>
{
// This is an implementation of "generational index" storage.
//
// The advantage of this storage method is extremely fast, O(1) lookup (way faster than Dictionary).
// Resolving a value in the storage is a single array load and generation compare. Extremely fast.
// Indices can also be cached into temporary
// Disadvantages are that storage cannot be shrunk, and sparse storage is inefficient space wise.
// Also this implementation does not have optimizations necessary to make sparse iteration efficient.
//
// The idea here is that the index type (NodeId in this case) has both an index and a generation.
// The index is an integer index into the storage array, the generation is used to avoid use-after-free.
//
// Empty slots in the array form a linked list of free slots.
// When we allocate a new slot, we pop one link off this linked list and hand out its index + generation.
//
// When we free a node, we bump the generation of the slot and make it the head of the linked list.
// The generation being bumped means that any IDs to this slot will fail to resolve (generation mismatch).
//
// Index of the next free slot to use when allocating a new one.
// If this is int.MaxValue,
// it basically means "no slot available" and the next allocation call should resize the array storage.
private int _nextFree = int.MaxValue;
private Slot[] _storage;
public int Count { get; private set; }
public ref T this[NodeId id]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref var slot = ref _storage[id.Index];
if (slot.Generation != id.Generation)
ThrowKeyNotFound();
return ref slot.Value;
}
}
public GenIdStorage()
{
_storage = Array.Empty<Slot>();
}
public static GenIdStorage<T> FromEnumerable(IEnumerable<(NodeId, T)> enumerable)
{
var storage = new GenIdStorage<T>();
// Cache enumerable to array to do double enumeration.
var cache = enumerable.ToArray();
if (cache.Length == 0)
return storage;
// Figure out max size necessary and set storage size to that.
var maxSize = cache.Max(tup => tup.Item1.Index) + 1;
storage._storage = new Slot[maxSize];
// Fill in slots.
foreach (var (id, value) in cache)
{
DebugTools.Assert(id.Generation != 0, "Generation cannot be 0");
ref var slot = ref storage._storage[id.Index];
DebugTools.Assert(slot.Generation == 0, "Duplicate key index!");
slot.Generation = id.Generation;
slot.Value = value;
}
// Go through empty slots and build the free chain.
var nextFree = int.MaxValue;
for (var i = 0; i < storage._storage.Length; i++)
{
ref var slot = ref storage._storage[i];
if (slot.Generation != 0)
// Slot in use.
continue;
slot.NextSlot = nextFree;
nextFree = i;
}
storage.Count = cache.Length;
storage._nextFree = nextFree;
return storage;
}
public ref T Allocate(out NodeId id)
{
if (_nextFree == int.MaxValue)
ReAllocate();
var idx = _nextFree;
ref var slot = ref _storage[idx];
Count += 1;
_nextFree = slot.NextSlot;
// NextSlot = -1 indicates filled.
slot.NextSlot = -1;
id = new NodeId(idx, slot.Generation);
return ref slot.Value;
}
public void Free(NodeId id)
{
var idx = id.Index;
ref var slot = ref _storage[idx];
if (slot.Generation != id.Generation)
ThrowKeyNotFound();
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
slot.Value = default!;
Count -= 1;
slot.Generation += 1;
slot.NextSlot = _nextFree;
_nextFree = idx;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ReAllocate()
{
var oldLength = _storage.Length;
var newLength = Math.Max(oldLength, 2) * 2;
ReAllocateTo(newLength);
}
private void ReAllocateTo(int newSize)
{
var oldLength = _storage.Length;
DebugTools.Assert(newSize >= oldLength, "Cannot shrink GenIdStorage");
Array.Resize(ref _storage, newSize);
for (var i = oldLength; i < newSize - 1; i++)
{
// Build linked list chain for newly allocated segment.
ref var slot = ref _storage[i];
slot.NextSlot = i + 1;
// Every slot starts at generation 1.
slot.Generation = 1;
}
_storage[^1].NextSlot = _nextFree;
_nextFree = oldLength;
}
public ValuesCollection Values => new(this);
private struct Slot
{
// Next link on the free list. if int.MaxValue then this is the tail.
// If negative, this slot is occupied.
public int NextSlot;
// Generation of this slot.
public int Generation;
public T Value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowKeyNotFound()
{
throw new KeyNotFoundException();
}
public readonly struct ValuesCollection : IReadOnlyCollection<T>
{
private readonly GenIdStorage<T> _owner;
public ValuesCollection(GenIdStorage<T> owner)
{
_owner = owner;
}
public Enumerator GetEnumerator()
{
return new Enumerator(_owner);
}
public int Count => _owner.Count;
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
public struct Enumerator : IEnumerator<T>
{
// Save the array in the enumerator here to avoid a few pointer dereferences.
private readonly Slot[] _owner;
private int _index;
public Enumerator(GenIdStorage<T> owner)
{
_owner = owner._storage;
Current = default!;
_index = -1;
}
public bool MoveNext()
{
while (true)
{
_index += 1;
if (_index >= _owner.Length)
return false;
ref var slot = ref _owner[_index];
if (slot.NextSlot < 0)
{
Current = slot.Value;
return true;
}
}
}
public void Reset()
{
_index = -1;
}
object IEnumerator.Current => Current!;
public T Current { get; private set; }
public void Dispose()
{
}
}
} }
} }
@@ -67,12 +332,12 @@ namespace Content.Server.Power.Pow3r
{ {
public override NodeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override NodeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
return new(reader.GetInt32()); return new NodeId(reader.GetInt64());
} }
public override void Write(Utf8JsonWriter writer, NodeId value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, NodeId value, JsonSerializerOptions options)
{ {
writer.WriteNumberValue(value.Id); writer.WriteNumberValue(value.Combined);
} }
} }
@@ -186,22 +451,8 @@ namespace Content.Server.Power.Pow3r
// "Supplying" means the network is connected to the OUTPUT port of the battery. // "Supplying" means the network is connected to the OUTPUT port of the battery.
[ViewVariables] public List<NodeId> BatteriesDischarging = new(); [ViewVariables] public List<NodeId> BatteriesDischarging = new();
// Calculation parameters used by GraphWalkSolver.
// Unused by BatteryRampPegSolver.
[JsonIgnore] public float LocalDemandTotal;
[JsonIgnore] public float LocalDemandMet;
[JsonIgnore] public float GroupDemandTotal;
[JsonIgnore] public float GroupDemandMet;
[ViewVariables] [JsonIgnore] public int Height; [ViewVariables] [JsonIgnore] public int Height;
[JsonIgnore] public bool HeightTouched; [JsonIgnore] public bool HeightTouched;
// Supply available this tick.
[JsonIgnore] public float AvailableSupplyTotal;
// Max theoretical supply assuming max ramp.
[JsonIgnore] public float TheoreticalSupplyTotal;
public float RemainingDemand => LocalDemandTotal - LocalDemandMet;
} }
} }
} }

View File

@@ -1,67 +0,0 @@
using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
namespace Content.Server.Sound.Components
{
[RegisterComponent]
public class LoopingLoopingSoundComponent : SharedLoopingSoundComponent
{
/// <summary>
/// Stops all sounds.
/// </summary>
/// <param name="channel">User that will be affected.</param>
public void StopAllSounds(INetChannel? channel)
{
SendNetworkMessage(new StopAllSoundsMessage(), channel);
}
/// <summary>
/// Stops a certain scheduled sound from playing.
/// </summary>
/// <param name="channel">User that will be affected.</param>
public void StopScheduledSound(string filename, INetChannel? channel)
{
SendNetworkMessage(new StopSoundScheduleMessage() {Filename = filename}, channel);
}
/// <summary>
/// Adds an scheduled sound to be played.
/// </summary>
/// <param name="channel">User that will be affected.</param>
public void AddScheduledSound(ScheduledSound schedule, INetChannel? channel)
{
SendNetworkMessage(new ScheduledSoundMessage() {Schedule = schedule}, channel);
}
public override void StopAllSounds()
{
StopAllSounds(null);
}
public override void StopScheduledSound(string filename)
{
StopScheduledSound(filename, null);
}
public override void AddScheduledSound(ScheduledSound schedule)
{
AddScheduledSound(schedule, null);
}
/// <summary>
/// Play an audio file following the entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="channel">User that will be affected.</param>
public void Play(string filename, AudioParams? audioParams = null, INetChannel? channel = null)
{
AddScheduledSound(new ScheduledSound()
{
Filename = filename,
AudioParams = audioParams,
}, channel);
}
}
}

View File

@@ -1,11 +1,13 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Chemistry.Metabolizable namespace Content.Shared.Chemistry.Metabolizable
{ {
/// <summary> /// <summary>
/// Default metabolism for reagents. Metabolizes the reagent with no effects /// Default metabolization for reagents. Returns the amount of reagents metabolized without applying effects.
/// Metabolizes reagents at a constant rate, limited by how much is available. Other classes are derived from
/// this class, so that they do not need their own metabolization quantity calculation.
/// </summary> /// </summary>
[DataDefinition] [DataDefinition]
public class DefaultMetabolizable : IMetabolizable public class DefaultMetabolizable : IMetabolizable
@@ -13,12 +15,22 @@ namespace Content.Shared.Chemistry.Metabolizable
/// <summary> /// <summary>
/// Rate of metabolism in units / second /// Rate of metabolism in units / second
/// </summary> /// </summary>
[DataField("rate")] [DataField("rate")] public ReagentUnit MetabolismRate { get; set; } = ReagentUnit.New(1);
public double MetabolismRate { get; set; } = 1;
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime) public virtual ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent)
{ {
return ReagentUnit.New(MetabolismRate * tickTime);
// How much reagent should we metabolize
// The default behaviour is to metabolize at a constant rate, independent of the quantity of reagents.
var amountMetabolized = MetabolismRate * tickTime;
// is that much reagent actually available?
if (availableReagent < amountMetabolized)
{
return availableReagent;
}
return amountMetabolized;
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Shared.Chemistry.Metabolizable namespace Content.Shared.Chemistry.Metabolizable
@@ -16,7 +16,8 @@ namespace Content.Shared.Chemistry.Metabolizable
/// <param name="solutionEntity">The entity containing the solution.</param> /// <param name="solutionEntity">The entity containing the solution.</param>
/// <param name="reagentId">The reagent id</param> /// <param name="reagentId">The reagent id</param>
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param> /// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
/// <param name="availableReagent">Reagent available to be metabolized.</param>
/// <returns>The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.</returns> /// <returns>The amount of reagent to be removed. The metabolizing organ should handle removing the reagent.</returns>
ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime); ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime, ReagentUnit availableReagent);
} }
} }

View File

@@ -12,7 +12,7 @@ namespace Content.Shared.Chemistry.Reagent
Ingestion, Ingestion,
} }
[DataDefinition] [ImplicitDataDefinitionForInheritors]
public abstract class ReagentEntityReaction public abstract class ReagentEntityReaction
{ {
[ViewVariables] [ViewVariables]

View File

@@ -0,0 +1,50 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
namespace Content.Shared.Hands.Components
{
[RegisterComponent]
[NetworkedComponent]
public sealed class HandVirtualPullComponent : Component
{
private EntityUid _pulledEntity;
public override string Name => "HandVirtualPull";
public EntityUid PulledEntity
{
get => _pulledEntity;
set
{
_pulledEntity = value;
Dirty();
}
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new VirtualPullComponentState(_pulledEntity);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not VirtualPullComponentState pullState)
return;
_pulledEntity = pullState.PulledEntity;
}
[Serializable, NetSerializable]
public sealed class VirtualPullComponentState : ComponentState
{
public readonly EntityUid PulledEntity;
public VirtualPullComponentState(EntityUid pulledEntity)
{
PulledEntity = pulledEntity;
}
}
}
}

View File

@@ -55,11 +55,11 @@ namespace Content.Shared.Hands.Components
} }
} }
} }
private string? _activeHand; private string? _activeHand;
[ViewVariables] [ViewVariables]
public IReadOnlyList<IReadOnlyHand> ReadOnlyHands => Hands; public readonly List<Hand> Hands = new();
protected readonly List<Hand> Hands = new();
/// <summary> /// <summary>
/// The amount of throw impulse per distance the player is from the throw target. /// The amount of throw impulse per distance the player is from the throw target.
@@ -89,7 +89,10 @@ namespace Content.Shared.Hands.Components
public virtual void HandsModified() public virtual void HandsModified()
{ {
// todo axe all this for ECS.
Dirty(); Dirty();
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this });
} }
public void AddHand(string handName, HandLocation handLocation) public void AddHand(string handName, HandLocation handLocation)
@@ -100,7 +103,7 @@ namespace Content.Shared.Hands.Components
var container = ContainerHelpers.CreateContainer<ContainerSlot>(Owner, handName); var container = ContainerHelpers.CreateContainer<ContainerSlot>(Owner, handName);
container.OccludesLight = false; container.OccludesLight = false;
Hands.Add(new Hand(handName, true, handLocation, container)); Hands.Add(new Hand(handName, handLocation, container));
if (ActiveHand == null) if (ActiveHand == null)
ActiveHand = handName; ActiveHand = handName;
@@ -125,48 +128,55 @@ namespace Content.Shared.Hands.Components
Hands.Remove(hand); Hands.Remove(hand);
if (ActiveHand == hand.Name) if (ActiveHand == hand.Name)
ActiveHand = ReadOnlyHands.FirstOrDefault()?.Name; ActiveHand = Hands.FirstOrDefault()?.Name;
HandCountChanged(); HandCountChanged();
HandsModified(); HandsModified();
} }
public bool HasHand(string handName)
{
foreach (var hand in Hands)
{
if (hand.Name == handName)
return true;
}
return false;
}
private Hand? GetHand(string handName)
{
foreach (var hand in Hands)
{
if (hand.Name == handName)
return hand;
}
return null;
}
private Hand? GetActiveHand() private Hand? GetActiveHand()
{ {
if (ActiveHand == null) if (ActiveHand == null)
return null; return null;
return GetHand(ActiveHand); return GetHandOrNull(ActiveHand);
} }
protected bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand) public bool HasHand(string handName)
{ {
foundHand = GetHand(handName); return TryGetHand(handName, out _);
return foundHand != null;
} }
protected bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand) public Hand? GetHandOrNull(string handName)
{
return TryGetHand(handName, out var hand) ? hand : null;
}
public Hand GetHand(string handName)
{
if (!TryGetHand(handName, out var hand))
throw new KeyNotFoundException($"Unable to find hand with name {handName}");
return hand;
}
public bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand)
{
foreach (var hand in Hands)
{
if (hand.Name == handName)
{
foundHand = hand;
return true;
};
}
foundHand = null;
return false;
}
public bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand)
{ {
activeHand = GetActiveHand(); activeHand = GetActiveHand();
return activeHand != null; return activeHand != null;
@@ -211,7 +221,7 @@ namespace Content.Shared.Hands.Components
public IEnumerable<IEntity> GetAllHeldEntities() public IEnumerable<IEntity> GetAllHeldEntities()
{ {
foreach (var hand in ReadOnlyHands) foreach (var hand in Hands)
{ {
if (hand.HeldEntity != null) if (hand.HeldEntity != null)
yield return hand.HeldEntity; yield return hand.HeldEntity;
@@ -416,7 +426,7 @@ namespace Content.Shared.Hands.Components
/// <summary> /// <summary>
/// Drops a hands contents to the target location. /// Drops a hands contents to the target location.
/// </summary> /// </summary>
private void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true) public void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true)
{ {
var heldEntity = hand.HeldEntity; var heldEntity = hand.HeldEntity;
@@ -538,16 +548,7 @@ namespace Content.Shared.Hands.Components
public bool CanPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true) public bool CanPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true)
{ {
if (!TryGetActiveHand(out var hand)) return ActiveHand != null && CanPickupEntity(ActiveHand, entity, checkActionBlocker);
return false;
if (checkActionBlocker && !PlayerCanPickup())
return false;
if (!CanInsertEntityIntoHand(hand, entity))
return false;
return true;
} }
/// <summary> /// <summary>
@@ -563,10 +564,7 @@ namespace Content.Shared.Hands.Components
public bool TryPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true) public bool TryPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true)
{ {
if (!TryGetActiveHand(out var hand)) return ActiveHand != null && TryPickupEntity(ActiveHand, entity, checkActionBlocker);
return false;
return TryPickupEntity(hand, entity, checkActionBlocker);
} }
/// <summary> /// <summary>
@@ -574,9 +572,6 @@ namespace Content.Shared.Hands.Components
/// </summary> /// </summary>
protected bool CanInsertEntityIntoHand(Hand hand, IEntity entity) protected bool CanInsertEntityIntoHand(Hand hand, IEntity entity)
{ {
if (!hand.Enabled)
return false;
var handContainer = hand.Container; var handContainer = hand.Container;
if (handContainer == null) if (handContainer == null)
return false; return false;
@@ -602,7 +597,7 @@ namespace Content.Shared.Hands.Components
/// <summary> /// <summary>
/// Puts an entity into the player's hand, assumes that the insertion is allowed. /// Puts an entity into the player's hand, assumes that the insertion is allowed.
/// </summary> /// </summary>
private void PutEntityIntoHand(Hand hand, IEntity entity) public void PutEntityIntoHand(Hand hand, IEntity entity)
{ {
var handContainer = hand.Container; var handContainer = hand.Container;
if (handContainer == null) if (handContainer == null)
@@ -658,7 +653,7 @@ namespace Content.Shared.Hands.Components
if (newActiveIndex > finalHandIndex) if (newActiveIndex > finalHandIndex)
newActiveIndex = 0; newActiveIndex = 0;
nextHand = ReadOnlyHands[newActiveIndex].Name; nextHand = Hands[newActiveIndex].Name;
return true; return true;
} }
@@ -752,7 +747,7 @@ namespace Content.Shared.Hands.Components
Hand? priorityHand = null; Hand? priorityHand = null;
if (priorityHandName != null) if (priorityHandName != null)
priorityHand = GetHand(priorityHandName); priorityHand = GetHandOrNull(priorityHandName);
return TryPutInAnyHand(entity, priorityHand, checkActionBlocker); return TryPutInAnyHand(entity, priorityHand, checkActionBlocker);
} }
@@ -793,43 +788,16 @@ namespace Content.Shared.Hands.Components
protected virtual void DoActivate(IEntity heldEntity) { } protected virtual void DoActivate(IEntity heldEntity) { }
protected virtual void HandlePickupAnimation(IEntity entity) { } protected virtual void HandlePickupAnimation(IEntity entity) { }
protected void EnableHand(Hand hand)
{
hand.Enabled = true;
Dirty();
}
protected void DisableHand(Hand hand)
{
hand.Enabled = false;
DropHeldEntityToFloor(hand, intentionalDrop: false);
Dirty();
}
} }
public interface IReadOnlyHand public class Hand
{ {
[ViewVariables]
public string Name { get; } public string Name { get; }
public bool Enabled { get; } [ViewVariables]
public HandLocation Location { get; } public HandLocation Location { get; }
public abstract IEntity? HeldEntity { get; }
}
public class Hand : IReadOnlyHand
{
[ViewVariables]
public string Name { get; set; }
[ViewVariables]
public bool Enabled { get; set; }
[ViewVariables]
public HandLocation Location { get; set; }
/// <summary> /// <summary>
/// The container used to hold the contents of this hand. Nullable because the client must get the containers via <see cref="ContainerManagerComponent"/>, /// The container used to hold the contents of this hand. Nullable because the client must get the containers via <see cref="ContainerManagerComponent"/>,
/// which may not be synced with the server when the client hands are created. /// which may not be synced with the server when the client hands are created.
@@ -840,37 +808,36 @@ namespace Content.Shared.Hands.Components
[ViewVariables] [ViewVariables]
public IEntity? HeldEntity => Container?.ContainedEntities?.FirstOrDefault(); public IEntity? HeldEntity => Container?.ContainedEntities?.FirstOrDefault();
public Hand(string name, bool enabled, HandLocation location, IContainer? container = null) public bool IsEmpty => HeldEntity == null;
public Hand(string name, HandLocation location, IContainer? container = null)
{ {
Name = name; Name = name;
Enabled = enabled;
Location = location; Location = location;
Container = container; Container = container;
} }
public HandState ToHandState() public HandState ToHandState()
{ {
return new(Name, Location, Enabled); return new(Name, Location);
} }
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class HandState public struct HandState
{ {
public string Name { get; } public string Name { get; }
public HandLocation Location { get; } public HandLocation Location { get; }
public bool Enabled { get; }
public HandState(string name, HandLocation location, bool enabled) public HandState(string name, HandLocation location)
{ {
Name = name; Name = name;
Location = location; Location = location;
Enabled = enabled;
} }
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class HandsComponentState : ComponentState public sealed class HandsComponentState : ComponentState
{ {
public HandState[] Hands { get; } public HandState[] Hands { get; }
public string? ActiveHand { get; } public string? ActiveHand { get; }
@@ -886,25 +853,20 @@ namespace Content.Shared.Hands.Components
/// A message that calls the use interaction on an item in hand, presumed for now the interaction will occur only on the active hand. /// A message that calls the use interaction on an item in hand, presumed for now the interaction will occur only on the active hand.
/// </summary> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class UseInHandMsg : ComponentMessage public sealed class UseInHandMsg : EntityEventArgs
{ {
public UseInHandMsg()
{
Directed = true;
}
} }
/// <summary> /// <summary>
/// A message that calls the activate interaction on the item in the specified hand. /// A message that calls the activate interaction on the item in the specified hand.
/// </summary> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class ActivateInHandMsg : ComponentMessage public class ActivateInHandMsg : EntityEventArgs
{ {
public string HandName { get; } public string HandName { get; }
public ActivateInHandMsg(string handName) public ActivateInHandMsg(string handName)
{ {
Directed = true;
HandName = handName; HandName = handName;
} }
} }
@@ -913,13 +875,12 @@ namespace Content.Shared.Hands.Components
/// Uses the item in the active hand on the item in the specified hand. /// Uses the item in the active hand on the item in the specified hand.
/// </summary> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class ClientAttackByInHandMsg : ComponentMessage public class ClientInteractUsingInHandMsg : EntityEventArgs
{ {
public string HandName { get; } public string HandName { get; }
public ClientAttackByInHandMsg(string handName) public ClientInteractUsingInHandMsg(string handName)
{ {
Directed = true;
HandName = handName; HandName = handName;
} }
} }
@@ -928,28 +889,12 @@ namespace Content.Shared.Hands.Components
/// Moves an item from one hand to the active hand. /// Moves an item from one hand to the active hand.
/// </summary> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class MoveItemFromHandMsg : ComponentMessage public class MoveItemFromHandMsg : EntityEventArgs
{ {
public string HandName { get; } public string HandName { get; }
public MoveItemFromHandMsg(string handName) public MoveItemFromHandMsg(string handName)
{ {
Directed = true;
HandName = handName;
}
}
/// <summary>
/// Sets the player's active hand to a specified hand.
/// </summary>
[Serializable, NetSerializable]
public class ClientChangedHandMsg : ComponentMessage
{
public string HandName { get; }
public ClientChangedHandMsg(string handName)
{
Directed = true;
HandName = handName; HandName = handName;
} }
} }
@@ -975,7 +920,7 @@ namespace Content.Shared.Hands.Components
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class PickupAnimationMessage : ComponentMessage public class PickupAnimationMessage : EntityEventArgs
{ {
public EntityUid EntityUid { get; } public EntityUid EntityUid { get; }
public EntityCoordinates InitialPosition { get; } public EntityCoordinates InitialPosition { get; }
@@ -983,10 +928,15 @@ namespace Content.Shared.Hands.Components
public PickupAnimationMessage(EntityUid entityUid, Vector2 pickupDirection, EntityCoordinates initialPosition) public PickupAnimationMessage(EntityUid entityUid, Vector2 pickupDirection, EntityCoordinates initialPosition)
{ {
Directed = true;
EntityUid = entityUid; EntityUid = entityUid;
PickupDirection = pickupDirection; PickupDirection = pickupDirection;
InitialPosition = initialPosition; InitialPosition = initialPosition;
} }
} }
[Serializable, NetSerializable]
public struct HandsModifiedMessage
{
public SharedHandsComponent Hands;
}
} }

View File

@@ -1,7 +1,6 @@
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using System; using System;
@@ -16,11 +15,7 @@ namespace Content.Shared.Hands
SubscribeLocalEvent<SharedHandsComponent, EntRemovedFromContainerMessage>(HandleContainerModified); SubscribeLocalEvent<SharedHandsComponent, EntRemovedFromContainerMessage>(HandleContainerModified);
SubscribeLocalEvent<SharedHandsComponent, EntInsertedIntoContainerMessage>(HandleContainerModified); SubscribeLocalEvent<SharedHandsComponent, EntInsertedIntoContainerMessage>(HandleContainerModified);
SubscribeLocalEvent<RequestSetHandEvent>(HandleSetHand); SubscribeAllEvent<RequestSetHandEvent>(HandleSetHand);
SubscribeNetworkEvent<RequestSetHandEvent>(HandleSetHand);
SubscribeLocalEvent<RequestDropHeldEntityEvent>(HandleDrop);
SubscribeNetworkEvent<RequestDropHeldEntityEvent>(HandleDrop);
} }
public void DropHandItems(IEntity entity, bool doMobChecks = true) public void DropHandItems(IEntity entity, bool doMobChecks = true)
@@ -38,14 +33,16 @@ namespace Content.Shared.Hands
eventBus.RaiseLocalEvent(uid, msg); eventBus.RaiseLocalEvent(uid, msg);
if (msg.Cancelled) return; if (msg.Cancelled)
return;
if (entity.TryGetContainerMan(out var containerManager)) if (entity.TryGetContainerMan(out var containerManager))
{ {
var parentMsg = new ContainedEntityDropHandItemsAttemptEvent(uid); var parentMsg = new ContainedEntityDropHandItemsAttemptEvent(uid);
eventBus.RaiseLocalEvent(containerManager.Owner.Uid, parentMsg); eventBus.RaiseLocalEvent(containerManager.Owner.Uid, parentMsg);
if (parentMsg.Cancelled) return; if (parentMsg.Cancelled)
return;
} }
DropAllItemsInHands(entity, doMobChecks); DropAllItemsInHands(entity, doMobChecks);
@@ -55,9 +52,9 @@ namespace Content.Shared.Hands
{ {
} }
private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) private static void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
{ {
var entity = eventArgs.SenderSession?.AttachedEntity; var entity = eventArgs.SenderSession.AttachedEntity;
if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands)) if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands))
return; return;
@@ -65,17 +62,13 @@ namespace Content.Shared.Hands
hands.ActiveHand = msg.HandName; hands.ActiveHand = msg.HandName;
} }
private void HandleDrop(RequestDropHeldEntityEvent msg, EntitySessionEventArgs eventArgs) protected virtual void HandleContainerModified(
EntityUid uid,
SharedHandsComponent component,
ContainerModifiedMessage args)
{ {
var entity = eventArgs.SenderSession?.AttachedEntity; component.Dirty();
if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands))
return;
hands.TryDropHand(msg.HandName, msg.DropTarget);
} }
protected abstract void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args);
} }
public sealed class ContainedEntityDropHandItemsAttemptEvent : CancellableEntityEventArgs public sealed class ContainedEntityDropHandItemsAttemptEvent : CancellableEntityEventArgs
@@ -103,21 +96,4 @@ namespace Content.Shared.Hands
HandName = handName; HandName = handName;
} }
} }
[Serializable, NetSerializable]
public class RequestDropHeldEntityEvent : EntityEventArgs
{
/// <summary>
/// The hand to drop from.
/// </summary>
public string HandName { get; }
public EntityCoordinates DropTarget { get; }
public RequestDropHeldEntityEvent(string handName, EntityCoordinates dropTarget)
{
HandName = handName;
DropTarget = dropTarget;
}
}
} }

View File

@@ -0,0 +1,53 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Shared.Interaction
{
/// <summary>
/// Raised directed on the used object when clicking on another object before an interaction is handled.
/// </summary>
[PublicAPI]
public class BeforeInteractEvent : HandledEntityEventArgs
{
/// <summary>
/// Entity that triggered the interaction.
/// </summary>
public IEntity User { get; }
/// <summary>
/// Entity that the user used to interact.
/// </summary>
public IEntity Used { get; }
/// <summary>
/// Entity that was interacted on. This can be null if the attack did not click on an entity.
/// </summary>
public IEntity? Target { get; }
/// <summary>
/// Location that the user clicked outside of their interaction range.
/// </summary>
public EntityCoordinates ClickLocation { get; }
/// <summary>
/// Is the click location close enough to reach by the player? This does not check for obstructions, just that the target is within
/// reach radius around the user.
/// </summary>
public bool CanReach { get; }
public BeforeInteractEvent(
IEntity user,
IEntity used,
IEntity? target,
EntityCoordinates clickLocation,
bool canReach)
{
User = user;
Used = used;
Target = target;
ClickLocation = clickLocation;
CanReach = canReach;
}
}
}

View File

@@ -3,7 +3,7 @@ using JetBrains.Annotations;
using Robust.Shared.Analyzers; using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Shared.DragDrop namespace Content.Shared.Interaction
{ {
/// <summary> /// <summary>
/// This interface gives components behavior when they're dropped by a mob. /// This interface gives components behavior when they're dropped by a mob.

View File

@@ -1,5 +1,9 @@
using System; using System;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Players;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -9,7 +13,8 @@ namespace Content.Shared.Light.Component
[Serializable, NetSerializable] [Serializable, NetSerializable]
public enum ExpendableLightVisuals public enum ExpendableLightVisuals
{ {
State State,
Behavior
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
@@ -21,8 +26,11 @@ namespace Content.Shared.Light.Component
Dead Dead
} }
[NetworkedComponent]
public abstract class SharedExpendableLightComponent: Robust.Shared.GameObjects.Component public abstract class SharedExpendableLightComponent: Robust.Shared.GameObjects.Component
{ {
public static readonly AudioParams LoopedSoundParams = new(0, 1, "Master", 62.5f, 1, true, 0.3f);
public sealed override string Name => "ExpendableLight"; public sealed override string Name => "ExpendableLight";
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)]
@@ -66,7 +74,7 @@ namespace Content.Shared.Light.Component
[ViewVariables] [ViewVariables]
[DataField("loopedSound")] [DataField("loopedSound")]
protected string LoopedSound { get; set; } = default!; public string LoopedSound { get; set; } = string.Empty;
[ViewVariables] [ViewVariables]
[DataField("dieSound")] [DataField("dieSound")]

View File

@@ -50,6 +50,8 @@ namespace Content.Shared.Pulling.Components
return; return;
} }
var eventBus = Owner.EntityManager.EventBus;
// New value. Abandon being pulled by any existing object. // New value. Abandon being pulled by any existing object.
if (_puller != null) if (_puller != null)
{ {
@@ -64,10 +66,9 @@ namespace Content.Shared.Pulling.Components
{ {
var message = new PullStoppedMessage(oldPullerPhysics, _physics); var message = new PullStoppedMessage(oldPullerPhysics, _physics);
oldPuller.SendMessage(null, message); eventBus.RaiseLocalEvent(oldPuller.Uid, message, broadcast: false);
Owner.SendMessage(null, message); eventBus.RaiseLocalEvent(Owner.Uid, message);
oldPuller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message);
_physics.WakeBody(); _physics.WakeBody();
} }
// else-branch warning is handled below // else-branch warning is handled below
@@ -125,14 +126,14 @@ namespace Content.Shared.Pulling.Components
var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics); var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics);
value.SendMessage(null, pullAttempt); eventBus.RaiseLocalEvent(value.Uid, pullAttempt, broadcast: false);
if (pullAttempt.Cancelled) if (pullAttempt.Cancelled)
{ {
return; return;
} }
Owner.SendMessage(null, pullAttempt); eventBus.RaiseLocalEvent(Owner.Uid, pullAttempt);
if (pullAttempt.Cancelled) if (pullAttempt.Cancelled)
{ {
@@ -147,10 +148,8 @@ namespace Content.Shared.Pulling.Components
var message = new PullStartedMessage(PullerPhysics, _physics); var message = new PullStartedMessage(PullerPhysics, _physics);
_puller.SendMessage(null, message); eventBus.RaiseLocalEvent(_puller.Uid, message, broadcast: false);
Owner.SendMessage(null, message); eventBus.RaiseLocalEvent(Owner.Uid, message);
_puller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message);
var union = PullerPhysics.GetWorldAABB().Union(_physics.GetWorldAABB()); var union = PullerPhysics.GetWorldAABB().Union(_physics.GetWorldAABB());
var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f; var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f;
@@ -335,29 +334,6 @@ namespace Content.Shared.Pulling.Components
Puller = entity; Puller = entity;
} }
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
if (message is not PullMessage pullMessage ||
pullMessage.Pulled.Owner != Owner)
{
return;
}
var pulledStatus = Owner.GetComponentOrNull<SharedAlertsComponent>();
switch (message)
{
case PullStartedMessage:
pulledStatus?.ShowAlert(AlertType.Pulled);
break;
case PullStoppedMessage:
pulledStatus?.ClearAlert(AlertType.Pulled);
break;
}
}
protected override void OnRemove() protected override void OnRemove()
{ {
TryStopPull(); TryStopPull();

View File

@@ -1,6 +1,4 @@
using Content.Shared.Alert; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Physics.Pull;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Component = Robust.Shared.GameObjects.Component; using Component = Robust.Shared.GameObjects.Component;
@@ -46,30 +44,5 @@ namespace Content.Shared.Pulling.Components
base.OnRemove(); base.OnRemove();
} }
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
if (message is not PullMessage pullMessage ||
pullMessage.Puller.Owner != Owner)
{
return;
}
SharedAlertsComponent? ownerStatus = Owner.GetComponentOrNull<SharedAlertsComponent>();
switch (message)
{
case PullStartedMessage msg:
Pulling = msg.Pulled.Owner;
ownerStatus?.ShowAlert(AlertType.Pulling);
break;
case PullStoppedMessage _:
Pulling = null;
ownerStatus?.ClearAlert(AlertType.Pulling);
break;
}
}
} }
} }

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Physics;
namespace Content.Shared.Physics.Pull namespace Content.Shared.Physics.Pull
{ {
public class PullMessage : ComponentMessage public class PullMessage : EntityEventArgs
{ {
public readonly IPhysBody Puller; public readonly IPhysBody Puller;
public readonly IPhysBody Pulled; public readonly IPhysBody Pulled;

View File

@@ -0,0 +1,44 @@
using Content.Shared.Alert;
using Content.Shared.Physics.Pull;
using Content.Shared.Pulling.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Shared.Pulling
{
[UsedImplicitly]
public sealed class SharedPullerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedPullerComponent, PullStartedMessage>(PullerHandlePullStarted);
SubscribeLocalEvent<SharedPullerComponent, PullStoppedMessage>(PullerHandlePullStopped);
}
private static void PullerHandlePullStarted(
EntityUid uid,
SharedPullerComponent component,
PullStartedMessage args)
{
if (args.Puller.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ShowAlert(AlertType.Pulling);
}
private static void PullerHandlePullStopped(
EntityUid uid,
SharedPullerComponent component,
PullStoppedMessage args)
{
if (args.Puller.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ClearAlert(AlertType.Pulling);
}
}
}

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Shared.Alert;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Physics.Pull; using Content.Shared.Physics.Pull;
@@ -56,12 +57,34 @@ namespace Content.Shared.Pulling
SubscribeLocalEvent<MoveEvent>(PullerMoved); SubscribeLocalEvent<MoveEvent>(PullerMoved);
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInsert); SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInsert);
SubscribeLocalEvent<SharedPullableComponent, PullStartedMessage>(PullableHandlePullStarted);
SubscribeLocalEvent<SharedPullableComponent, PullStoppedMessage>(PullableHandlePullStopped);
CommandBinds.Builder CommandBinds.Builder
.Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject))
.Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject)) .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject))
.Register<SharedPullingSystem>(); .Register<SharedPullingSystem>();
} }
// Raise a "you are being pulled" alert if the pulled entity has alerts.
private static void PullableHandlePullStarted(EntityUid uid, SharedPullableComponent component, PullStartedMessage args)
{
if (args.Pulled.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ShowAlert(AlertType.Pulled);
}
private static void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args)
{
if (args.Pulled.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ClearAlert(AlertType.Pulled);
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);

View File

@@ -1,116 +0,0 @@
using System;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Sound
{
[NetworkedComponent()]
public class SharedLoopingSoundComponent : Component
{
public override string Name => "LoopingSound";
/// <summary>
/// Stops all sounds.
/// </summary>
public virtual void StopAllSounds()
{}
/// <summary>
/// Stops a certain scheduled sound from playing.
/// </summary>
public virtual void StopScheduledSound(string filename)
{}
/// <summary>
/// Adds an scheduled sound to be played.
/// </summary>
public virtual void AddScheduledSound(ScheduledSound scheduledSound)
{}
/// <summary>
/// Play an audio file following the entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
public void Play(string filename, AudioParams? audioParams = null)
{
AddScheduledSound(new ScheduledSound()
{
Filename = filename,
AudioParams = audioParams,
});
}
}
[NetSerializable, Serializable]
public class ScheduledSoundMessage : ComponentMessage
{
public ScheduledSound Schedule = new();
public ScheduledSoundMessage()
{
Directed = true;
}
}
[NetSerializable, Serializable]
public class StopSoundScheduleMessage : ComponentMessage
{
public string Filename = string.Empty;
public StopSoundScheduleMessage()
{
Directed = true;
}
}
[NetSerializable, Serializable]
public class StopAllSoundsMessage : ComponentMessage
{
public StopAllSoundsMessage()
{
Directed = true;
}
}
[Serializable, NetSerializable]
[DataDefinition]
public class ScheduledSound
{
[DataField("fileName")]
public string Filename = string.Empty;
/// <summary>
/// The parameters to play the sound with.
/// </summary>
[DataField("audioparams")]
public AudioParams? AudioParams;
/// <summary>
/// Delay in milliseconds before playing the sound,
/// and delay between repetitions if Times is not 0.
/// </summary>
[DataField("delay")]
public uint Delay;
/// <summary>
/// Maximum number of milliseconds to add to the delay randomly.
/// Useful for random ambience noises. Generated value differs from client to client.
/// </summary>
[DataField("randomdelay")]
public uint RandomDelay;
/// <summary>
/// How many times to repeat the sound. If it's 0, it will play the sound once.
/// If it's less than 0, it will repeat the sound indefinitely.
/// If it's greater than 0, it will play the sound n+1 times.
/// </summary>
[DataField("times")]
public int Times;
/// <summary>
/// Whether the sound will play or not.
/// </summary>
public bool Play = true;
}
}

View File

@@ -7,7 +7,7 @@ using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Sound namespace Content.Shared.Sound
{ {
[DataDefinition] [ImplicitDataDefinitionForInheritors]
public abstract class SoundSpecifier public abstract class SoundSpecifier
{ {
public abstract string GetSound(); public abstract string GetSound();
@@ -15,7 +15,6 @@ namespace Content.Shared.Sound
public abstract bool TryGetSound([NotNullWhen(true)] out string? sound); public abstract bool TryGetSound([NotNullWhen(true)] out string? sound);
} }
[DataDefinition]
public sealed class SoundPathSpecifier : SoundSpecifier public sealed class SoundPathSpecifier : SoundSpecifier
{ {
public const string Node = "path"; public const string Node = "path";
@@ -49,7 +48,6 @@ namespace Content.Shared.Sound
} }
} }
[DataDefinition]
public sealed class SoundCollectionSpecifier : SoundSpecifier public sealed class SoundCollectionSpecifier : SoundSpecifier
{ {
public const string Node = "collection"; public const string Node = "collection";

View File

@@ -20,15 +20,14 @@ namespace Pow3r
return; return;
_paused = dat.Paused; _paused = dat.Paused;
_nextId = dat.NextId;
_currentSolver = dat.Solver; _currentSolver = dat.Solver;
_state = new PowerState _state = new PowerState
{ {
Networks = dat.Networks.ToDictionary(n => n.Id, n => n), Networks = GenIdStorage.FromEnumerable(dat.Networks.Select(n => (n.Id, n))),
Supplies = dat.Supplies.ToDictionary(s => s.Id, s => s), Supplies = GenIdStorage.FromEnumerable(dat.Supplies.Select(s => (s.Id, s))),
Loads = dat.Loads.ToDictionary(l => l.Id, l => l), Loads = GenIdStorage.FromEnumerable(dat.Loads.Select(l => (l.Id, l))),
Batteries = dat.Batteries.ToDictionary(b => b.Id, b => b) Batteries = GenIdStorage.FromEnumerable(dat.Batteries.Select(b => (b.Id, b)))
}; };
_displayLoads = dat.Loads.ToDictionary(n => n.Id, _ => new DisplayLoad()); _displayLoads = dat.Loads.ToDictionary(n => n.Id, _ => new DisplayLoad());
@@ -44,7 +43,6 @@ namespace Pow3r
var data = new DiskDat var data = new DiskDat
{ {
Paused = _paused, Paused = _paused,
NextId = _nextId,
Solver = _currentSolver, Solver = _currentSolver,
Loads = _state.Loads.Values.ToList(), Loads = _state.Loads.Values.ToList(),
@@ -59,7 +57,6 @@ namespace Pow3r
private sealed class DiskDat private sealed class DiskDat
{ {
public bool Paused; public bool Paused;
public int NextId;
public int Solver; public int Solver;
public List<Load> Loads; public List<Load> Loads;

View File

@@ -10,7 +10,6 @@ namespace Pow3r
{ {
private const int MaxTickData = 180; private const int MaxTickData = 180;
private int _nextId = 1;
private PowerState _state = new(); private PowerState _state = new();
private Network _linking; private Network _linking;
private int _tickDataIdx; private int _tickDataIdx;
@@ -18,13 +17,11 @@ namespace Pow3r
private readonly string[] _solverNames = private readonly string[] _solverNames =
{ {
nameof(GraphWalkSolver),
nameof(BatteryRampPegSolver), nameof(BatteryRampPegSolver),
nameof(NoOpSolver) nameof(NoOpSolver)
}; };
private readonly IPowerSolver[] _solvers = { private readonly IPowerSolver[] _solvers = {
new GraphWalkSolver(),
new BatteryRampPegSolver(), new BatteryRampPegSolver(),
new NoOpSolver() new NoOpSolver()
}; };
@@ -35,11 +32,6 @@ namespace Pow3r
private readonly Queue<object> _remQueue = new(); private readonly Queue<object> _remQueue = new();
private readonly Stopwatch _simStopwatch = new Stopwatch(); private readonly Stopwatch _simStopwatch = new Stopwatch();
private NodeId AllocId()
{
return new(_nextId++);
}
private void Tick(float frameTime) private void Tick(float frameTime)
{ {
if (_paused) if (_paused)

View File

@@ -36,30 +36,30 @@ namespace Pow3r
if (Button("Generator")) if (Button("Generator"))
{ {
var id = AllocId(); var supply = new Supply();
_state.Supplies.Add(id, new Supply { Id = id }); _state.Supplies.Allocate(out supply.Id) = supply;
_displaySupplies.Add(id, new DisplaySupply()); _displaySupplies.Add(supply.Id, new DisplaySupply());
} }
if (Button("Load")) if (Button("Load"))
{ {
var id = AllocId(); var load = new Load();
_state.Loads.Add(id, new Load { Id = id }); _state.Loads.Allocate(out load.Id) = load;
_displayLoads.Add(id, new DisplayLoad()); _displayLoads.Add(load.Id, new DisplayLoad());
} }
if (Button("Network")) if (Button("Network"))
{ {
var id = AllocId(); var network = new Network();
_state.Networks.Add(id, new Network { Id = id }); _state.Networks.Allocate(out network.Id) = network;
_displayNetworks.Add(id, new DisplayNetwork()); _displayNetworks.Add(network.Id, new DisplayNetwork());
} }
if (Button("Battery")) if (Button("Battery"))
{ {
var id = AllocId(); var battery = new Battery();
_state.Batteries.Add(id, new Battery { Id = id }); _state.Batteries.Allocate(out battery.Id) = battery;
_displayBatteries.Add(id, new DisplayBattery()); _displayBatteries.Add(battery.Id, new DisplayBattery());
} }
Checkbox("Paused", ref _paused); Checkbox("Paused", ref _paused);
@@ -355,25 +355,25 @@ namespace Pow3r
switch (item) switch (item)
{ {
case Network n: case Network n:
_state.Networks.Remove(n.Id); _state.Networks.Free(n.Id);
_displayNetworks.Remove(n.Id); _displayNetworks.Remove(n.Id);
reLink = true; reLink = true;
break; break;
case Supply s: case Supply s:
_state.Supplies.Remove(s.Id); _state.Supplies.Free(s.Id);
_state.Networks.Values.ForEach(n => n.Supplies.Remove(s.Id)); _state.Networks.Values.ForEach(n => n.Supplies.Remove(s.Id));
_displaySupplies.Remove(s.Id); _displaySupplies.Remove(s.Id);
break; break;
case Load l: case Load l:
_state.Loads.Remove(l.Id); _state.Loads.Free(l.Id);
_state.Networks.Values.ForEach(n => n.Loads.Remove(l.Id)); _state.Networks.Values.ForEach(n => n.Loads.Remove(l.Id));
_displayLoads.Remove(l.Id); _displayLoads.Remove(l.Id);
break; break;
case Battery b: case Battery b:
_state.Batteries.Remove(b.Id); _state.Batteries.Free(b.Id);
_state.Networks.Values.ForEach(n => n.BatteriesCharging.Remove(b.Id)); _state.Networks.Values.ForEach(n => n.BatteriesCharging.Remove(b.Id));
_state.Networks.Values.ForEach(n => n.BatteriesDischarging.Remove(b.Id)); _state.Networks.Values.ForEach(n => n.BatteriesDischarging.Remove(b.Id));
_displayBatteries.Remove(b.Id); _displayBatteries.Remove(b.Id);

View File

@@ -1613,3 +1613,10 @@ Entries:
- {message: Added bio suits into bio lockers, type: Add} - {message: Added bio suits into bio lockers, type: Add}
id: 287 id: 287
time: '2021-07-28T17:36:06.0000000+00:00' time: '2021-07-28T17:36:06.0000000+00:00'
- author: PJB3005
changes:
- {message: Pulling is better integrated with hands now. It properly picks an empty
hand and dropping on the pulling hand stops the pull. You also see what you're
pulling in your hand!, type: Tweak}
id: 288
time: '2021-07-31T01:14:00.0000000+00:00'

View File

@@ -47,7 +47,6 @@
- type: PointLight - type: PointLight
enabled: false enabled: false
radius: 3 radius: 3
- type: LoopingSound
- type: Appearance - type: Appearance
visuals: visuals:
- type: FlashLightVisualizer - type: FlashLightVisualizer

View File

@@ -15,7 +15,6 @@
- type: PointLight - type: PointLight
enabled: false enabled: false
radius: 3 radius: 3
- type: LoopingSound
- type: Appearance - type: Appearance
visuals: visuals:
- type: FlashLightVisualizer - type: FlashLightVisualizer

View File

@@ -19,7 +19,6 @@
sprite: Clothing/Shoes/Specific/clown.rsi sprite: Clothing/Shoes/Specific/clown.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Shoes/Specific/clown.rsi sprite: Clothing/Shoes/Specific/clown.rsi
- type: LoopingSound
- type: FootstepModifier - type: FootstepModifier
footstepSoundCollection: footstep_clown footstepSoundCollection: footstep_clown

View File

@@ -10,7 +10,6 @@
- type: Puddle - type: Puddle
spill_sound: /Audio/Effects/Fluids/splat.ogg spill_sound: /Audio/Effects/Fluids/splat.ogg
recolor: true recolor: true
- type: LoopingSound
- type: Clickable - type: Clickable
- type: Slippery - type: Slippery
- type: Physics - type: Physics

View File

@@ -387,7 +387,6 @@
# Eek! You can eat them alive for now until someone makes something that detects when # Eek! You can eat them alive for now until someone makes something that detects when
# a mob is dead or something idk # a mob is dead or something idk
- type: Food - type: Food
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
contents: contents:
reagents: reagents:

View File

@@ -9,7 +9,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/Baked/bread.rsi sprite: Objects/Consumable/Food/Baked/bread.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 20 maxVol: 20
contents: contents:

View File

@@ -10,7 +10,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/Baked/cake.rsi sprite: Objects/Consumable/Food/Baked/cake.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 20 maxVol: 20
contents: contents:

View File

@@ -13,7 +13,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/Baked/donut.rsi sprite: Objects/Consumable/Food/Baked/donut.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 5 maxVol: 5
contents: contents:

View File

@@ -9,7 +9,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/Baked/misc.rsi sprite: Objects/Consumable/Food/Baked/misc.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 5 maxVol: 5
contents: contents:

View File

@@ -10,7 +10,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/Baked/pizza.rsi sprite: Objects/Consumable/Food/Baked/pizza.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 15 maxVol: 15
contents: contents:

View File

@@ -92,7 +92,6 @@
- box11 - box11
- box12 - box12
# Someday... # Someday...
# - type: LoopingSound
# - type: DamageOnLand # - type: DamageOnLand
# amount: 5 # amount: 5
# - type: DamageOtherOnHit # - type: DamageOtherOnHit

View File

@@ -78,7 +78,6 @@
reagents: reagents:
- ReagentId: Nutriment - ReagentId: Nutriment
Quantity: 15 Quantity: 15
- type: LoopingSound
- type: Food - type: Food
trash: FoodTinPeachesTrash trash: FoodTinPeachesTrash
@@ -127,7 +126,6 @@
reagents: reagents:
- ReagentId: Nutriment - ReagentId: Nutriment
Quantity: 15 Quantity: 15
- type: LoopingSound
- type: Food - type: Food
trash: FoodTinPeachesMaintTrash trash: FoodTinPeachesMaintTrash
@@ -176,7 +174,6 @@
reagents: reagents:
- ReagentId: Nutriment - ReagentId: Nutriment
Quantity: 15 Quantity: 15
- type: LoopingSound
- type: Food - type: Food
trash: FoodTinBeansTrash trash: FoodTinBeansTrash
@@ -227,7 +224,6 @@
reagents: reagents:
- ReagentId: Nutriment - ReagentId: Nutriment
Quantity: 15 Quantity: 15
- type: LoopingSound
- type: Food - type: Food
trash: FoodTinMRETrash trash: FoodTinMRETrash

View File

@@ -10,7 +10,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/burger.rsi sprite: Objects/Consumable/Food/burger.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 15 maxVol: 15
contents: contents:

View File

@@ -23,7 +23,6 @@
reagents: reagents:
- ReagentId: Egg - ReagentId: Egg
Quantity: 5 Quantity: 5
- type: LoopingSound
- type: DamageOnLand - type: DamageOnLand
amount: 1 amount: 1
- type: DamageOtherOnHit - type: DamageOtherOnHit

View File

@@ -9,7 +9,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/frozen.rsi sprite: Objects/Consumable/Food/frozen.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 20 # For sprinkles or something? Idk. maxVol: 20 # For sprinkles or something? Idk.
contents: contents:

View File

@@ -9,7 +9,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/meat.rsi sprite: Objects/Consumable/Food/meat.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 20 maxVol: 20
contents: contents:

View File

@@ -11,7 +11,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/noodles.rsi sprite: Objects/Consumable/Food/noodles.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 20 maxVol: 20
contents: contents:

View File

@@ -10,7 +10,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/skewer.rsi sprite: Objects/Consumable/Food/skewer.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 20 maxVol: 20
contents: contents:

View File

@@ -9,7 +9,6 @@
- type: Sprite - type: Sprite
sprite: Objects/Consumable/Food/snacks.rsi sprite: Objects/Consumable/Food/snacks.rsi
netsync: false netsync: false
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 30 # Room for extra condiments maxVol: 30 # Room for extra condiments
contents: contents:

View File

@@ -5,7 +5,6 @@
components: components:
- type: Food - type: Food
trash: FoodBowlBig trash: FoodBowlBig
- type: LoopingSound
- type: SolutionContainer - type: SolutionContainer
maxVol: 20 maxVol: 20
contents: contents:

View File

@@ -6,7 +6,6 @@
id: DrinkBottleBaseEmpty id: DrinkBottleBaseEmpty
description: That's an empty bottle. description: That's an empty bottle.
components: components:
- type: LoopingSound
- type: Sprite - type: Sprite
state: icon state: icon
- type: SolutionContainer - type: SolutionContainer

View File

@@ -28,7 +28,6 @@
interfaces: interfaces:
- key: enum.PDAUiKey.Key - key: enum.PDAUiKey.Key
type: PDABoundUserInterface type: PDABoundUserInterface
- type: LoopingSound
- type: entity - type: entity
parent: BasePDA parent: BasePDA

View File

@@ -10,7 +10,6 @@
- type: Item - type: Item
sprite: Objects/Misc/skub.rsi sprite: Objects/Misc/skub.rsi
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/skub.ogg path: /Audio/Items/skub.ogg

View File

@@ -13,7 +13,6 @@
- type: EmitSoundOnActivate - type: EmitSoundOnActivate
sound: sound:
collection: ToySqueak collection: ToySqueak
- type: LoopingSound
- type: ItemCooldown - type: ItemCooldown
- type: UseDelay - type: UseDelay
delay: 1.0 delay: 1.0
@@ -91,7 +90,6 @@
sprite: Objects/Fun/toys.rsi sprite: Objects/Fun/toys.rsi
state: plushie_snake state: plushie_snake
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/rattle.ogg path: /Audio/Items/Toys/rattle.ogg
@@ -108,7 +106,6 @@
sprite: Objects/Fun/toys.rsi sprite: Objects/Fun/toys.rsi
state: toy_mouse state: toy_mouse
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/mousesqueek.ogg path: /Audio/Items/Toys/mousesqueek.ogg
@@ -125,7 +122,6 @@
sprite: Objects/Fun/toys.rsi sprite: Objects/Fun/toys.rsi
state: plushie_vox state: plushie_vox
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Voice/Vox/shriek1.ogg path: /Audio/Voice/Vox/shriek1.ogg
@@ -151,7 +147,6 @@
sound: sound:
path: /Audio/Items/Toys/helpme.ogg path: /Audio/Items/Toys/helpme.ogg
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/helpme.ogg path: /Audio/Items/Toys/helpme.ogg
@@ -173,7 +168,6 @@
sound: sound:
path: /Audio/Items/Toys/hellothere.ogg path: /Audio/Items/Toys/hellothere.ogg
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/hellothere.ogg path: /Audio/Items/Toys/hellothere.ogg
@@ -195,7 +189,6 @@
sound: sound:
path: /Audio/Items/Toys/thankyou.ogg path: /Audio/Items/Toys/thankyou.ogg
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/thankyou.ogg path: /Audio/Items/Toys/thankyou.ogg
@@ -217,7 +210,6 @@
sound: sound:
path: /Audio/Items/Toys/verygood.ogg path: /Audio/Items/Toys/verygood.ogg
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/verygood.ogg path: /Audio/Items/Toys/verygood.ogg
@@ -239,7 +231,6 @@
sound: sound:
path: /Audio/Items/Toys/imsorry.ogg path: /Audio/Items/Toys/imsorry.ogg
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/imsorry.ogg path: /Audio/Items/Toys/imsorry.ogg
@@ -311,7 +302,6 @@
sprite: Objects/Fun/toys.rsi sprite: Objects/Fun/toys.rsi
state: ian state: ian
- type: ItemCooldown - type: ItemCooldown
- type: LoopingSound
- type: EmitSoundOnUse - type: EmitSoundOnUse
sound: sound:
path: /Audio/Items/Toys/ian.ogg path: /Audio/Items/Toys/ian.ogg

View File

@@ -29,7 +29,6 @@
- type: Item - type: Item
sprite: Objects/Misc/torch.rsi sprite: Objects/Misc/torch.rsi
HeldPrefix: unlit HeldPrefix: unlit
- type: LoopingSound
- type: Construction - type: Construction
graph: lightTorch graph: lightTorch
node: torch node: torch

View File

@@ -32,7 +32,6 @@
sprite: Objects/Misc/flare.rsi sprite: Objects/Misc/flare.rsi
color: "#FF0000" color: "#FF0000"
HeldPrefix: unlit HeldPrefix: unlit
- type: LoopingSound
- type: Appearance - type: Appearance
visuals: visuals:
- type: ExpendableLightVisualizer - type: ExpendableLightVisualizer

View File

@@ -23,7 +23,6 @@
- type: PointLight - type: PointLight
enabled: false enabled: false
radius: 3 radius: 3
- type: LoopingSound
- type: Appearance - type: Appearance
visuals: visuals:
- type: FlashLightVisualizer - type: FlashLightVisualizer

View File

@@ -23,7 +23,6 @@
radius: 3 radius: 3
energy: 2.5 energy: 2.5
color: "#FFC458" color: "#FFC458"
- type: LoopingSound
- type: Appearance - type: Appearance
visuals: visuals:
- type: LanternVisualizer - type: LanternVisualizer
@@ -39,4 +38,3 @@
radius: 5 radius: 5
energy: 10 energy: 10
color: "#FFC458" color: "#FFC458"
- type: LoopingSound

View File

@@ -17,4 +17,3 @@
size: 24 size: 24
sprite: Objects/Weapons/Melee/pickaxe.rsi sprite: Objects/Weapons/Melee/pickaxe.rsi
prefix: inhand prefix: inhand
- type: LoopingSound

View File

@@ -26,7 +26,6 @@
interfaces: interfaces:
- key: enum.ReagentDispenserUiKey.Key - key: enum.ReagentDispenserUiKey.Key
type: ReagentDispenserBoundUserInterface type: ReagentDispenserBoundUserInterface
- type: LoopingSound
- type: Anchorable - type: Anchorable
- type: Pullable - type: Pullable
- type: Damageable - type: Damageable

View File

@@ -11,7 +11,6 @@
- type: Appearance - type: Appearance
visuals: visuals:
- type: MicrowaveVisualizer - type: MicrowaveVisualizer
- type: LoopingSound
- type: UserInterface - type: UserInterface
interfaces: interfaces:
- key: enum.MicrowaveUiKey.Key - key: enum.MicrowaveUiKey.Key

View File

@@ -14,7 +14,6 @@
- type: Appearance - type: Appearance
visuals: visuals:
- type: ReagentGrinderVisualizer - type: ReagentGrinderVisualizer
- type: LoopingSound
- type: Physics - type: Physics
fixtures: fixtures:
- shape: - shape:

View File

@@ -57,4 +57,3 @@
- type: StorageVisualizer - type: StorageVisualizer
state_open: generic_open state_open: generic_open
state_closed: generic_door state_closed: generic_door
- type: LoopingSound

Some files were not shown because too many files have changed in this diff Show More