diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs
index a25353f798..a1575636cd 100644
--- a/Content.Client/Entry/IgnoredComponents.cs
+++ b/Content.Client/Entry/IgnoredComponents.cs
@@ -275,6 +275,9 @@ namespace Content.Client.Entry
"Advertise",
"PowerNetworkBattery",
"BatteryCharger",
+ "UnpoweredFlashlight",
+ "Uplink",
+ "PDA",
"SpawnItemsOnUse",
"Wieldable",
"IncreaseDamageOnWield",
diff --git a/Content.Client/PDA/PDABoundUserInterface.cs b/Content.Client/PDA/PDABoundUserInterface.cs
index b445d16358..23b4ae1166 100644
--- a/Content.Client/PDA/PDABoundUserInterface.cs
+++ b/Content.Client/PDA/PDABoundUserInterface.cs
@@ -1,19 +1,13 @@
-using System;
-using Content.Client.Examine;
using Content.Client.Message;
using Content.Shared.PDA;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
-using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.PDA
@@ -21,11 +15,7 @@ namespace Content.Client.PDA
[UsedImplicitly]
public class PDABoundUserInterface : BoundUserInterface
{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
-
private PDAMenu? _menu;
- private PDAMenuPopup? _failPopup;
public PDABoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
@@ -35,7 +25,7 @@ namespace Content.Client.PDA
{
base.Open();
SendMessage(new PDARequestUpdateInterfaceMessage());
- _menu = new PDAMenu(this, _prototypeManager);
+ _menu = new PDAMenu();
_menu.OpenToLeft();
_menu.OnClose += Close;
_menu.FlashLightToggleButton.OnToggled += _ =>
@@ -43,7 +33,7 @@ namespace Content.Client.PDA
SendMessage(new PDAToggleFlashlightMessage());
};
- _menu.EjectIDButton.OnPressed += _ =>
+ _menu.EjectIdButton.OnPressed += _ =>
{
SendMessage(new PDAEjectIDMessage());
};
@@ -53,37 +43,11 @@ namespace Content.Client.PDA
SendMessage(new PDAEjectPenMessage());
};
- _menu.MasterTabContainer.OnTabChanged += i =>
+ _menu.ActivateUplinkButton.OnPressed += _ =>
{
- var tab = _menu.MasterTabContainer.GetChild(i);
- if (tab == _menu.UplinkTabContainer)
- {
- SendMessage(new PDARequestUpdateInterfaceMessage());
- }
+ SendMessage(new PDAShowUplinkMessage());
};
- _menu.OnListingButtonPressed += (_, listing) =>
- {
- if (_menu.CurrentLoggedInAccount?.DataBalance < listing.Price)
- {
- _failPopup = new PDAMenuPopup(Loc.GetString("pda-bound-user-interface-insufficient-funds-popup"));
- _userInterfaceManager.ModalRoot.AddChild(_failPopup);
- _failPopup.Open(UIBox2.FromDimensions(_menu.Position.X + 150, _menu.Position.Y + 60, 156, 24));
- _menu.OnClose += () =>
- {
- _failPopup.Dispose();
- };
- }
-
- SendMessage(new PDAUplinkBuyListingMessage(listing.ItemId));
- };
-
- _menu.OnCategoryButtonPressed += (_, category) =>
- {
- _menu.CurrentFilterCategory = category;
- SendMessage(new PDARequestUpdateInterfaceMessage());
-
- };
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -103,45 +67,26 @@ namespace Content.Client.PDA
if (msg.PDAOwnerInfo.ActualOwnerName != null)
{
- _menu.PDAOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
+ _menu.PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
("ActualOwnerName", msg.PDAOwnerInfo.ActualOwnerName)));
}
if (msg.PDAOwnerInfo.IdOwner != null || msg.PDAOwnerInfo.JobTitle != null)
{
- _menu.IDInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui",
+ _menu.IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui",
("Owner",msg.PDAOwnerInfo.IdOwner ?? "Unknown"),
("JobTitle",msg.PDAOwnerInfo.JobTitle ?? "Unassigned")));
}
else
{
- _menu.IDInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui-blank"));
+ _menu.IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui-blank"));
}
- _menu.EjectIDButton.Visible = msg.PDAOwnerInfo.IdOwner != null || msg.PDAOwnerInfo.JobTitle != null;
+ _menu.EjectIdButton.Visible = msg.PDAOwnerInfo.IdOwner != null || msg.PDAOwnerInfo.JobTitle != null;
_menu.EjectPenButton.Visible = msg.HasPen;
+ _menu.ActivateUplinkButton.Visible = msg.HasUplink;
- if (msg.Account != null)
- {
- _menu.CurrentLoggedInAccount = msg.Account;
- var balance = msg.Account.DataBalance;
- var weightedColor = GetWeightedColorString(balance);
- _menu.BalanceInfo.SetMarkup(Loc.GetString("pda-bound-user-interface-tc-balance-popup",
- ("weightedColor",weightedColor),
- ("balance",balance)));
- }
-
- if (msg.Listings != null)
- {
- _menu.ClearListings();
- foreach (var item in msg.Listings) //Should probably chunk these out instead. to-do if this clogs the internet tubes.
- {
- _menu.AddListingGui(item);
- }
- }
-
- _menu.MasterTabContainer.SetTabVisible(1, msg.Account != null);
break;
}
}
@@ -156,410 +101,5 @@ namespace Content.Client.PDA
_menu?.Dispose();
}
-
- ///
- /// This is shitcode. It is, however, "PJB-approved shitcode".
- ///
- ///
- ///
- public static Color GetWeightedColor(int x)
- {
- var weightedColor = Color.Gray;
- if (x <= 0)
- {
- return weightedColor;
- }
- if (x <= 5)
- {
- weightedColor = Color.LimeGreen;
- }
- else if (x > 5 && x < 10)
- {
- weightedColor = Color.Yellow;
- }
- else if (x > 10 && x <= 20)
- {
- weightedColor = Color.Orange;
- }
- else if (x > 20 && x <= 50)
- {
- weightedColor = Color.Purple;
- }
-
- return weightedColor;
- }
-
- public static string GetWeightedColorString(int x)
- {
- var weightedColor = "gray";
- if (x <= 0)
- {
- return weightedColor;
- }
-
- if (x <= 5)
- {
- weightedColor = "green";
- }
- else if (x > 5 && x < 10)
- {
- weightedColor = "yellow";
- }
- else if (x > 10 && x <= 20)
- {
- weightedColor = "yellow";
- }
- else if (x > 20 && x <= 50)
- {
- weightedColor = "purple";
- }
- return weightedColor;
- }
-
- public sealed class PDAMenuPopup : Popup
- {
- public PDAMenuPopup(string text)
- {
- var label = new RichTextLabel();
- label.SetMessage(text);
- AddChild(new PanelContainer
- {
- StyleClasses = { ExamineSystem.StyleClassEntityTooltip },
- Children = { label }
- });
- }
- }
-
- private class PDAMenu : SS14Window
- {
- private PDABoundUserInterface _owner { get; }
-
- public Button FlashLightToggleButton { get; }
- public Button EjectIDButton { get; }
- public Button EjectPenButton { get; }
-
- public readonly TabContainer MasterTabContainer;
-
- public RichTextLabel PDAOwnerLabel { get; }
- public PanelContainer IDInfoContainer { get; }
- public RichTextLabel IDInfoLabel { get; }
-
- public BoxContainer UplinkTabContainer { get; }
-
- protected readonly SplitContainer CategoryAndListingsContainer;
-
- private readonly IPrototypeManager _prototypeManager;
-
- public readonly BoxContainer UplinkListingsContainer;
-
- public readonly BoxContainer CategoryListContainer;
- public readonly RichTextLabel BalanceInfo;
- public event Action? OnListingButtonPressed;
- public event Action? OnCategoryButtonPressed;
-
- public UplinkCategory CurrentFilterCategory
- {
- get => _currentFilter;
- set
- {
- if (value.GetType() != typeof(UplinkCategory))
- {
- return;
- }
-
- _currentFilter = value;
- }
- }
-
- public UplinkAccountData? CurrentLoggedInAccount
- {
- get => _loggedInUplinkAccount;
- set => _loggedInUplinkAccount = value;
- }
-
- private UplinkCategory _currentFilter;
- private UplinkAccountData? _loggedInUplinkAccount;
-
- public PDAMenu(PDABoundUserInterface owner, IPrototypeManager prototypeManager)
- {
- MinSize = SetSize = (512, 256);
-
- _owner = owner;
- _prototypeManager = prototypeManager;
- Title = Loc.GetString("comp-pda-ui-menu-title");
-
- #region MAIN_MENU_TAB
- //Main menu
- PDAOwnerLabel = new RichTextLabel
- {
- };
-
- IDInfoLabel = new RichTextLabel()
- {
- HorizontalExpand = true,
- };
-
- EjectIDButton = new Button
- {
- Text = Loc.GetString("comp-pda-ui-eject-id-button"),
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center
- };
- EjectPenButton = new Button
- {
- Text = Loc.GetString("comp-pda-ui-eject-pen-button"),
- HorizontalAlignment = HAlignment.Center,
- VerticalAlignment = VAlignment.Center
- };
-
- var innerHBoxContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Children =
- {
- IDInfoLabel,
- EjectIDButton,
- EjectPenButton
- }
- };
-
- IDInfoContainer = new PanelContainer
- {
- Children =
- {
- innerHBoxContainer,
- }
- };
-
- FlashLightToggleButton = new Button
- {
- Text = Loc.GetString("comp-pda-ui-toggle-flashlight-button"),
- ToggleMode = true,
- };
-
- var mainMenuTabContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- VerticalExpand = true,
- HorizontalExpand = true,
- MinSize = (50, 50),
-
- Children =
- {
- PDAOwnerLabel,
- IDInfoContainer,
- FlashLightToggleButton
- }
- };
-
- #endregion
-
- #region UPLINK_TAB
- //Uplink Tab
- CategoryListContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
-
- BalanceInfo = new RichTextLabel
- {
- HorizontalAlignment = HAlignment.Center,
- };
-
- //Red background container.
- var masterPanelContainer = new PanelContainer
- {
- PanelOverride = new StyleBoxFlat { BackgroundColor = Color.Black },
- VerticalExpand = true
- };
-
- //This contains both the panel of the category buttons and the listings box.
- CategoryAndListingsContainer = new SplitContainer
- {
- Orientation = SplitContainer.SplitOrientation.Horizontal,
- VerticalExpand = true,
- };
-
-
- var uplinkShopScrollContainer = new ScrollContainer
- {
- HorizontalExpand = true,
- VerticalExpand = true,
- SizeFlagsStretchRatio = 2,
- };
-
- //Add the category list to the left side. The store items to center.
- var categoryListContainerBackground = new PanelContainer
- {
- PanelOverride = new StyleBoxFlat { BackgroundColor = Color.Gray.WithAlpha(0.02f) },
- VerticalExpand = true,
- Children =
- {
- CategoryListContainer
- }
- };
-
- CategoryAndListingsContainer.AddChild(categoryListContainerBackground);
- CategoryAndListingsContainer.AddChild(uplinkShopScrollContainer);
- masterPanelContainer.AddChild(CategoryAndListingsContainer);
-
- //Actual list of buttons for buying a listing from the uplink.
- UplinkListingsContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- HorizontalExpand = true,
- VerticalExpand = true,
- SizeFlagsStretchRatio = 2,
- MinSize = (100, 256),
- };
- uplinkShopScrollContainer.AddChild(UplinkListingsContainer);
-
- var innerVboxContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- VerticalExpand = true,
-
- Children =
- {
- BalanceInfo,
- masterPanelContainer
- }
- };
-
- UplinkTabContainer = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Children =
- {
- innerVboxContainer
- }
- };
- PopulateUplinkCategoryButtons();
- #endregion
-
- //The master menu that contains all of the tabs.
- MasterTabContainer = new TabContainer
- {
- Children =
- {
- mainMenuTabContainer,
- }
- };
-
- //Add all the tabs to the Master container.
- MasterTabContainer.SetTabTitle(0, Loc.GetString("pda-bound-user-interface-main-menu-tab-title"));
- MasterTabContainer.AddChild(UplinkTabContainer);
- MasterTabContainer.SetTabTitle(1, Loc.GetString("pda-bound-user-interface-uplink-tab-title"));
- Contents.AddChild(MasterTabContainer);
- }
-
- private void PopulateUplinkCategoryButtons()
- {
-
- foreach (UplinkCategory cat in Enum.GetValues(typeof(UplinkCategory)))
- {
-
- var catButton = new PDAUplinkCategoryButton
- {
- Text = Loc.GetString(cat.ToString()),
- ButtonCategory = cat
-
- };
- //It'd be neat if it could play a cool tech ping sound when you switch categories,
- //but right now there doesn't seem to be an easy way to do client-side audio without still having to round trip to the server and
- //send to a specific client INetChannel.
- catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.ButtonCategory);
-
- CategoryListContainer.AddChild(catButton);
- }
-
- }
-
- public void AddListingGui(UplinkListingData listing)
- {
- if (!_prototypeManager.TryIndex(listing.ItemId, out EntityPrototype? prototype) || listing.Category != CurrentFilterCategory)
- {
- return;
- }
- var weightedColor = GetWeightedColor(listing.Price);
- var itemLabel = new Label
- {
- Text = listing.ListingName == string.Empty ? prototype.Name : listing.ListingName,
- ToolTip = listing.Description == string.Empty ? prototype.Description : listing.Description,
- HorizontalExpand = true,
- Modulate = _loggedInUplinkAccount?.DataBalance >= listing.Price
- ? Color.White
- : Color.Gray.WithAlpha(0.30f)
- };
-
- var priceLabel = new Label
- {
- Text = $"{listing.Price} TC",
- HorizontalAlignment = HAlignment.Right,
- Modulate = _loggedInUplinkAccount?.DataBalance >= listing.Price
- ? weightedColor
- : Color.Gray.WithAlpha(0.30f)
- };
-
- //Padding for the price lable.
- var pricePadding = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- MinSize = (32, 1),
- };
-
- //Contains the name of the item and its price. Used for spacing item name and price.
- var listingButtonHbox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Children =
- {
- itemLabel,
- priceLabel,
- pricePadding
- }
- };
-
- var listingButtonPanelContainer = new PanelContainer
- {
- Children =
- {
- listingButtonHbox
- }
- };
-
- var pdaUplinkListingButton = new PDAUplinkItemButton(listing)
- {
- Children =
- {
- listingButtonPanelContainer
- }
- };
- pdaUplinkListingButton.OnPressed += args
- => OnListingButtonPressed?.Invoke(args, pdaUplinkListingButton.ButtonListing);
- UplinkListingsContainer.AddChild(pdaUplinkListingButton);
- }
-
- public void ClearListings()
- {
- UplinkListingsContainer.Children.Clear();
- }
-
- private sealed class PDAUplinkItemButton : ContainerButton
- {
- public PDAUplinkItemButton(UplinkListingData data)
- {
- ButtonListing = data;
- }
-
- public UplinkListingData ButtonListing { get; }
- }
-
- private sealed class PDAUplinkCategoryButton : Button
- {
- public UplinkCategory ButtonCategory;
-
- }
- }
}
}
diff --git a/Content.Client/PDA/PDAComponent.cs b/Content.Client/PDA/PDAComponent.cs
deleted file mode 100644
index 353e9675a5..0000000000
--- a/Content.Client/PDA/PDAComponent.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Content.Shared.PDA;
-using Content.Shared.Sound;
-using Robust.Shared.Audio;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Network;
-using Robust.Shared.Player;
-using Robust.Shared.Players;
-using Robust.Shared.Serialization.Manager.Attributes;
-using Robust.Shared.ViewVariables;
-
-namespace Content.Client.PDA
-{
- [RegisterComponent]
- public class PDAComponent : SharedPDAComponent
- {
- [ViewVariables]
- [DataField("buySuccessSound")]
- private SoundSpecifier BuySuccessSound { get; } = new SoundPathSpecifier("/Audio/Effects/kaching.ogg");
-
- [ViewVariables]
- [DataField("insufficientFundsSound")]
- private SoundSpecifier InsufficientFundsSound { get; } = new SoundPathSpecifier("/Audio/Effects/error.ogg");
-
- public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
- {
- base.HandleNetworkMessage(message, netChannel, session);
- switch (message)
- {
- case PDAUplinkBuySuccessMessage:
- SoundSystem.Play(Filter.Local(), BuySuccessSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f));
- break;
-
- case PDAUplinkInsufficientFundsMessage:
- SoundSystem.Play(Filter.Local(), InsufficientFundsSound.GetSound(), Owner, AudioParams.Default);
- break;
- }
- }
- }
-}
diff --git a/Content.Client/PDA/PDAMenu.xaml b/Content.Client/PDA/PDAMenu.xaml
new file mode 100644
index 0000000000..e477c94577
--- /dev/null
+++ b/Content.Client/PDA/PDAMenu.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/PDA/PDAMenu.xaml.cs b/Content.Client/PDA/PDAMenu.xaml.cs
new file mode 100644
index 0000000000..c25b209b74
--- /dev/null
+++ b/Content.Client/PDA/PDAMenu.xaml.cs
@@ -0,0 +1,28 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Localization;
+
+namespace Content.Client.PDA
+{
+ [GenerateTypedNameReferences]
+ public partial class PDAMenu : SS14Window
+ {
+ public Button FlashLightToggleButton => FlashLightToggleButtonProtected;
+ public Button EjectIdButton => EjectIdButtonProtected;
+ public Button EjectPenButton => EjectPenButtonProtected;
+
+ public Button ActivateUplinkButton => ActivateUplinkButtonProtected;
+
+ public RichTextLabel PdaOwnerLabel => PdaOwnerLabelProtected;
+ public RichTextLabel IdInfoLabel => IdInfoLabelProtected;
+
+ public PDAMenu()
+ {
+ RobustXamlLoader.Load(this);
+
+ MasterTabContainer.SetTabTitle(0, Loc.GetString("pda-bound-user-interface-main-menu-tab-title"));
+ }
+ }
+}
diff --git a/Content.Client/PDA/PDAVisualizer.cs b/Content.Client/PDA/PDAVisualizer.cs
index 36d4c403f3..927ad7f041 100644
--- a/Content.Client/PDA/PDAVisualizer.cs
+++ b/Content.Client/PDA/PDAVisualizer.cs
@@ -1,4 +1,5 @@
-using Content.Shared.PDA;
+using Content.Shared.Light;
+using Content.Shared.PDA;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
@@ -46,9 +47,9 @@ namespace Content.Client.PDA
var sprite = component.Owner.GetComponent();
sprite.LayerSetVisible(PDAVisualLayers.Flashlight, false);
- if (component.TryGetData(PDAVisuals.FlashlightLit, out bool isScreenLit))
+ if (component.TryGetData(UnpoweredFlashlightVisuals.LightOn, out bool isFlashlightOn))
{
- sprite.LayerSetVisible(PDAVisualLayers.Flashlight, isScreenLit);
+ sprite.LayerSetVisible(PDAVisualLayers.Flashlight, isFlashlightOn);
}
if (component.TryGetData(PDAVisuals.IDCardInserted, out bool isCardInserted))
diff --git a/Content.Client/Traitor/Uplink/UplinkBoundUserInterface.cs b/Content.Client/Traitor/Uplink/UplinkBoundUserInterface.cs
new file mode 100644
index 0000000000..171fe488fe
--- /dev/null
+++ b/Content.Client/Traitor/Uplink/UplinkBoundUserInterface.cs
@@ -0,0 +1,112 @@
+using Content.Client.Examine;
+using Content.Client.Message;
+using Content.Shared.Traitor.Uplink;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Traitor.Uplink
+{
+ [UsedImplicitly]
+ public class UplinkBoundUserInterface : BoundUserInterface
+ {
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
+
+ private UplinkMenu? _menu;
+ private UplinkMenuPopup? _failPopup;
+
+ public UplinkBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ _menu = new UplinkMenu(_prototypeManager);
+ _menu.OpenCentered();
+ _menu.OnClose += Close;
+
+ _menu.OnListingButtonPressed += (_, listing) =>
+ {
+ if (_menu.CurrentLoggedInAccount?.DataBalance < listing.Price)
+ {
+ _failPopup = new UplinkMenuPopup(Loc.GetString("uplink-bound-user-interface-insufficient-funds-popup"));
+ _userInterfaceManager.ModalRoot.AddChild(_failPopup);
+ _failPopup.Open(UIBox2.FromDimensions(_menu.Position.X + 150, _menu.Position.Y + 60, 156, 24));
+ _menu.OnClose += () =>
+ {
+ _failPopup.Dispose();
+ };
+ }
+
+ SendMessage(new UplinkBuyListingMessage(listing.ItemId));
+ };
+
+ _menu.OnCategoryButtonPressed += (_, category) =>
+ {
+ _menu.CurrentFilterCategory = category;
+ SendMessage(new UplinkRequestUpdateInterfaceMessage());
+
+ };
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ if (_menu == null)
+ {
+ return;
+ }
+
+ switch (state)
+ {
+ case UplinkUpdateState msg:
+ {
+ _menu.CurrentLoggedInAccount = msg.Account;
+ var balance = msg.Account.DataBalance;
+ string weightedColor = balance switch
+ {
+ <= 0 => "gray",
+ <= 5 => "green",
+ <= 20 => "yellow",
+ <= 50 => "purple",
+ _ => "gray"
+ };
+ _menu.BalanceInfo.SetMarkup(Loc.GetString("uplink-bound-user-interface-tc-balance-popup",
+ ("weightedColor", weightedColor),
+ ("balance", balance)));
+
+ _menu.ClearListings();
+ foreach (var item in
+ msg.Listings) //Should probably chunk these out instead. to-do if this clogs the internet tubes.
+ {
+ _menu.AddListingGui(item);
+ }
+
+ break;
+ }
+ }
+ }
+
+ private sealed class UplinkMenuPopup : Popup
+ {
+ public UplinkMenuPopup(string text)
+ {
+ var label = new RichTextLabel();
+ label.SetMessage(text);
+ AddChild(new PanelContainer
+ {
+ StyleClasses = { ExamineSystem.StyleClassEntityTooltip },
+ Children = { label }
+ });
+ }
+ }
+ }
+}
diff --git a/Content.Client/Traitor/Uplink/UplinkMenu.xaml b/Content.Client/Traitor/Uplink/UplinkMenu.xaml
new file mode 100644
index 0000000000..1694bc568c
--- /dev/null
+++ b/Content.Client/Traitor/Uplink/UplinkMenu.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Traitor/Uplink/UplinkMenu.xaml.cs b/Content.Client/Traitor/Uplink/UplinkMenu.xaml.cs
new file mode 100644
index 0000000000..5908c3efa1
--- /dev/null
+++ b/Content.Client/Traitor/Uplink/UplinkMenu.xaml.cs
@@ -0,0 +1,166 @@
+using System;
+using Content.Shared.PDA;
+using Content.Shared.Traitor.Uplink;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Traitor.Uplink
+{
+ [GenerateTypedNameReferences]
+ public partial class UplinkMenu : SS14Window
+ {
+ private readonly IPrototypeManager _prototypeManager;
+
+ public RichTextLabel BalanceInfo => BalanceInfoProtected;
+ public event Action? OnListingButtonPressed;
+ public event Action? OnCategoryButtonPressed;
+
+ public UplinkMenu(IPrototypeManager prototypeManager)
+ {
+ RobustXamlLoader.Load(this);
+
+ _prototypeManager = prototypeManager;
+
+ PopulateUplinkCategoryButtons();
+ }
+
+ public UplinkCategory CurrentFilterCategory
+ {
+ get => _currentFilter;
+ set
+ {
+ if (value.GetType() != typeof(UplinkCategory))
+ {
+ return;
+ }
+
+ _currentFilter = value;
+ }
+ }
+
+ public UplinkAccountData? CurrentLoggedInAccount
+ {
+ get => _loggedInUplinkAccount;
+ set => _loggedInUplinkAccount = value;
+ }
+
+ private UplinkCategory _currentFilter;
+ private UplinkAccountData? _loggedInUplinkAccount;
+
+ public void AddListingGui(UplinkListingData listing)
+ {
+ if (!_prototypeManager.TryIndex(listing.ItemId, out EntityPrototype? prototype) || listing.Category != CurrentFilterCategory)
+ {
+ return;
+ }
+ var weightedColor = listing.Price switch
+ {
+ <= 0 => Color.Gray,
+ <= 5 => Color.Green,
+ <= 10 => Color.Yellow,
+ <= 20 => Color.Orange,
+ <= 50 => Color.Purple,
+ _ => Color.Gray
+ };
+ var itemLabel = new Label
+ {
+ Text = listing.ListingName == string.Empty ? prototype.Name : listing.ListingName,
+ ToolTip = listing.Description == string.Empty ? prototype.Description : listing.Description,
+ HorizontalExpand = true,
+ Modulate = _loggedInUplinkAccount?.DataBalance >= listing.Price
+ ? Color.White
+ : Color.Gray.WithAlpha(0.30f)
+ };
+
+ var priceLabel = new Label
+ {
+ Text = $"{listing.Price} TC",
+ HorizontalAlignment = HAlignment.Right,
+ Modulate = _loggedInUplinkAccount?.DataBalance >= listing.Price
+ ? weightedColor
+ : Color.Gray.WithAlpha(0.30f)
+ };
+
+ //Padding for the price lable.
+ var pricePadding = new BoxContainer
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ MinSize = (32, 1),
+ };
+
+ //Contains the name of the item and its price. Used for spacing item name and price.
+ var listingButtonHbox = new BoxContainer
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ Children =
+ {
+ itemLabel,
+ priceLabel,
+ pricePadding
+ }
+ };
+
+ var listingButtonPanelContainer = new PanelContainer
+ {
+ Children =
+ {
+ listingButtonHbox
+ }
+ };
+
+ var pdaUplinkListingButton = new PDAUplinkItemButton(listing)
+ {
+ Children =
+ {
+ listingButtonPanelContainer
+ }
+ };
+ pdaUplinkListingButton.OnPressed += args
+ => OnListingButtonPressed?.Invoke(args, pdaUplinkListingButton.ButtonListing);
+ UplinkListingsContainer.AddChild(pdaUplinkListingButton);
+ }
+
+ public void ClearListings()
+ {
+ UplinkListingsContainer.Children.Clear();
+ }
+
+ private void PopulateUplinkCategoryButtons()
+ {
+ foreach (UplinkCategory cat in Enum.GetValues(typeof(UplinkCategory)))
+ {
+ var catButton = new PDAUplinkCategoryButton
+ {
+ Text = Loc.GetString(cat.ToString()),
+ ButtonCategory = cat
+ };
+ //It'd be neat if it could play a cool tech ping sound when you switch categories,
+ //but right now there doesn't seem to be an easy way to do client-side audio without still having to round trip to the server and
+ //send to a specific client INetChannel.
+ catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.ButtonCategory);
+
+ CategoryListContainer.AddChild(catButton);
+ }
+ }
+
+ private sealed class PDAUplinkItemButton : ContainerButton
+ {
+ public PDAUplinkItemButton(UplinkListingData data)
+ {
+ ButtonListing = data;
+ }
+
+ public UplinkListingData ButtonListing { get; }
+ }
+
+ private sealed class PDAUplinkCategoryButton : Button
+ {
+ public UplinkCategory ButtonCategory;
+ }
+ }
+}
diff --git a/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs b/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs
index 73027ea48a..dd7e344fb7 100644
--- a/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs
+++ b/Content.IntegrationTests/Tests/PDA/PDAExtensionsTests.cs
@@ -1,6 +1,7 @@
-using System.Linq;
+using System.Linq;
using System.Threading.Tasks;
using Content.Server.Access.Components;
+using Content.Shared.Containers.ItemSlots;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
@@ -28,8 +29,13 @@ namespace Content.IntegrationTests.Tests.PDA
id: {PdaDummy}
name: {PdaDummy}
components:
+ - type: ItemSlots
+ slots:
+ pda_id_slot:
+ whitelist:
+ components:
+ - IdCard
- type: PDA
- idCard: {IdCardDummy}
- type: Item";
[Test]
@@ -69,13 +75,17 @@ namespace Content.IntegrationTests.Tests.PDA
player.GetComponent().PutInHand(pdaItemComponent);
var pdaComponent = dummyPda.GetComponent();
- var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, player.Transform.MapPosition).GetComponent();
- pdaComponent.InsertIdCard(pdaIdCard);
+ var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, player.Transform.MapPosition);
+
+ var itemSlots = dummyPda.GetComponent();
+ sEntityManager.EntitySysManager.GetEntitySystem()
+ .TryInsertContent(itemSlots, pdaIdCard, PDAComponent.IDSlotName);
var pdaContainedId = pdaComponent.ContainedID;
// The PDA in the hand should be found first
Assert.NotNull(player.GetHeldId());
Assert.True(player.TryGetHeldId(out id));
+
Assert.NotNull(id);
Assert.That(id, Is.EqualTo(pdaContainedId));
diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs
index 7ad42dbabd..2927f9298f 100644
--- a/Content.Server/Entry/EntryPoint.cs
+++ b/Content.Server/Entry/EntryPoint.cs
@@ -87,7 +87,7 @@ namespace Content.Server.Entry
IoCManager.Resolve().Initialize();
IoCManager.Resolve().Initialize();
IoCManager.Resolve().Initialize();
- IoCManager.Resolve().Initialize();
+ IoCManager.Resolve().Initialize();
IoCManager.Resolve().Initialize();
IoCManager.Resolve().Initialize();
IoCManager.Resolve().Initialize();
diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs
index 5d696d81ea..8e3fe42989 100644
--- a/Content.Server/GameTicking/GameTicker.Spawning.cs
+++ b/Content.Server/GameTicking/GameTicker.Spawning.cs
@@ -234,7 +234,8 @@ namespace Content.Server.GameTicking
var access = card.Owner.GetComponent();
var accessTags = access.Tags;
accessTags.UnionWith(jobPrototype.Access);
- pdaComponent.SetPDAOwner(characterName);
+ EntityManager.EntitySysManager.GetEntitySystem()
+ .SetOwner(pdaComponent, characterName);
}
#endregion
diff --git a/Content.Server/GameTicking/Presets/PresetSuspicion.cs b/Content.Server/GameTicking/Presets/PresetSuspicion.cs
index 195e5aceac..80186871ac 100644
--- a/Content.Server/GameTicking/Presets/PresetSuspicion.cs
+++ b/Content.Server/GameTicking/Presets/PresetSuspicion.cs
@@ -1,17 +1,19 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules;
using Content.Server.Inventory.Components;
using Content.Server.Items;
-using Content.Server.PDA;
+using Content.Server.PDA.Managers;
using Content.Server.Players;
using Content.Server.Suspicion;
using Content.Server.Suspicion.Roles;
-using Content.Shared;
+using Content.Server.Traitor.Uplink;
+using Content.Server.Traitor.Uplink.Components;
+using Content.Server.Traitor.Uplink.Systems;
using Content.Shared.CCVar;
using Content.Shared.Inventory;
-using Content.Shared.PDA;
using Content.Shared.Roles;
+using Content.Shared.Traitor.Uplink;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -31,6 +33,8 @@ namespace Content.Server.GameTicking.Presets
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] protected readonly IEntityManager EntityManager = default!;
+ [Dependency] private readonly IUplinkManager _uplinkManager = default!;
public int MinPlayers { get; set; }
public int MinTraitors { get; set; }
@@ -109,28 +113,17 @@ namespace Content.Server.GameTicking.Presets
var traitorRole = new SuspicionTraitorRole(mind!, antagPrototype);
mind!.AddRole(traitorRole);
traitors.Add(traitorRole);
+
// creadth: we need to create uplink for the antag.
// PDA should be in place already, so we just need to
// initiate uplink account.
- var uplinkAccount =
- new UplinkAccount(mind.OwnedEntity!.Uid,
- TraitorStartingBalance);
- var inventory = mind.OwnedEntity.GetComponent();
- if (!inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent? pdaItem))
- {
+ var uplinkAccount = new UplinkAccount(mind.OwnedEntity!.Uid, TraitorStartingBalance);
+ _uplinkManager.AddNewAccount(uplinkAccount);
+
+ // try to place uplink
+ if (!EntityManager.EntitySysManager.GetEntitySystem()
+ .AddUplink(mind.OwnedEntity, uplinkAccount))
continue;
- }
-
- var pda = pdaItem.Owner;
-
- var pdaComponent = pda.GetComponent();
- if (pdaComponent.IdSlotEmpty)
- {
- continue;
- }
-
- pdaComponent.InitUplinkAccount(uplinkAccount);
-
}
foreach (var player in list)
diff --git a/Content.Server/GameTicking/Presets/PresetTraitor.cs b/Content.Server/GameTicking/Presets/PresetTraitor.cs
index 509410abe3..b135f0a94b 100644
--- a/Content.Server/GameTicking/Presets/PresetTraitor.cs
+++ b/Content.Server/GameTicking/Presets/PresetTraitor.cs
@@ -7,13 +7,16 @@ using Content.Server.Inventory.Components;
using Content.Server.Items;
using Content.Server.Objectives.Interfaces;
using Content.Server.PDA;
+using Content.Server.PDA.Managers;
using Content.Server.Players;
using Content.Server.Traitor;
-using Content.Shared;
+using Content.Server.Traitor.Uplink;
+using Content.Server.Traitor.Uplink.Components;
+using Content.Server.Traitor.Uplink.Systems;
using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Content.Shared.Inventory;
-using Content.Shared.PDA;
+using Content.Shared.Traitor.Uplink;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -34,6 +37,8 @@ namespace Content.Server.GameTicking.Presets
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] protected readonly IEntityManager EntityManager = default!;
+ [Dependency] private readonly IUplinkManager _uplinkManager = default!;
public override string ModeTitle => Loc.GetString("traitor-title");
@@ -120,27 +125,15 @@ namespace Content.Server.GameTicking.Presets
DebugTools.AssertNotNull(mind.OwnedEntity);
var uplinkAccount = new UplinkAccount(mind.OwnedEntity!.Uid, StartingBalance);
- var inventory = mind.OwnedEntity.GetComponent();
- if (!inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent? pdaItem))
- {
- Logger.ErrorS("preset", "Failed getting pda for picked traitor.");
- continue;
- }
+ _uplinkManager.AddNewAccount(uplinkAccount);
- var pda = pdaItem.Owner;
-
- var pdaComponent = pda.GetComponent();
- if (pdaComponent.IdSlotEmpty)
- {
- Logger.ErrorS("preset","PDA had no id for picked traitor");
+ if (!EntityManager.EntitySysManager.GetEntitySystem()
+ .AddUplink(mind.OwnedEntity, uplinkAccount))
continue;
- }
var traitorRole = new TraitorRole(mind);
-
mind.AddRole(traitorRole);
_traitors.Add(traitorRole);
- pdaComponent.InitUplinkAccount(uplinkAccount);
}
var adjectives = _prototypeManager.Index("adjectives").Values;
diff --git a/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs b/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs
index 706daef1f4..07236033f8 100644
--- a/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs
+++ b/Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules;
@@ -17,7 +16,6 @@ using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.Inventory;
using Content.Shared.MobState;
-using Content.Shared.PDA;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -27,6 +25,11 @@ using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Random;
+using Content.Server.Traitor.Uplink.Components;
+using Content.Shared.Traitor.Uplink;
+using Content.Server.PDA.Managers;
+using Content.Server.Traitor.Uplink;
+using Content.Server.Traitor.Uplink.Systems;
using Content.Shared.Damage.Prototypes;
namespace Content.Server.GameTicking.Presets
@@ -40,6 +43,7 @@ namespace Content.Server.GameTicking.Presets
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IUplinkManager _uplinkManager = default!;
public string PDAPrototypeName => "CaptainPDA";
public string BeltPrototypeName => "ClothingBeltJanitorFilled";
@@ -103,12 +107,16 @@ namespace Content.Server.GameTicking.Presets
// Like normal traitors, they need access to a traitor account.
var uplinkAccount = new UplinkAccount(mind.OwnedEntity.Uid, startingBalance);
- var pdaComponent = newPDA.GetComponent();
- pdaComponent.InitUplinkAccount(uplinkAccount);
+ _uplinkManager.AddNewAccount(uplinkAccount);
+ _entityManager.EntitySysManager.GetEntitySystem()
+ .AddUplink(mind.OwnedEntity, uplinkAccount, newPDA);
+
_allOriginalNames[uplinkAccount] = mind.OwnedEntity.Name;
// The PDA needs to be marked with the correct owner.
- pdaComponent.SetPDAOwner(mind.OwnedEntity.Name);
+ var pda = newPDA.GetComponent();
+ _entityManager.EntitySysManager.GetEntitySystem()
+ .SetOwner(pda, mind.OwnedEntity.Name);
newPDA.AddComponent().UserId = mind.UserId;
}
@@ -217,14 +225,14 @@ namespace Content.Server.GameTicking.Presets
{
var lines = new List();
lines.Add("traitor-death-match-end-round-description-first-line");
- foreach (var pda in _entityManager.EntityQuery())
+ foreach (var uplink in _entityManager.EntityQuery(true))
{
- var uplink = pda.SyndicateUplinkAccount;
- if (uplink != null && _allOriginalNames.ContainsKey(uplink))
+ var uplinkAcc = uplink.UplinkAccount;
+ if (uplinkAcc != null && _allOriginalNames.ContainsKey(uplinkAcc))
{
lines.Add(Loc.GetString("traitor-death-match-end-round-description-entry",
- ("originalName", _allOriginalNames[uplink]),
- ("tcBalance", uplink.Balance)));
+ ("originalName", _allOriginalNames[uplinkAcc]),
+ ("tcBalance", uplinkAcc.Balance)));
}
}
return string.Join('\n', lines);
diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs
index 2723693706..b14b399ced 100644
--- a/Content.Server/IoC/ServerContentIoC.cs
+++ b/Content.Server/IoC/ServerContentIoC.cs
@@ -42,7 +42,7 @@ namespace Content.Server.IoC
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
- IoCManager.Register();
+ IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
diff --git a/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs b/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs
new file mode 100644
index 0000000000..7d4bac7b21
--- /dev/null
+++ b/Content.Server/Light/Components/UnpoweredFlashlightComponent.cs
@@ -0,0 +1,44 @@
+using Content.Server.Light.EntitySystems;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Sound;
+using Content.Shared.Verbs;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Localization;
+using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Server.Light.Components
+{
+ ///
+ /// This is simplified version of .
+ /// It doesn't consume any power and can be toggle only by verb.
+ ///
+ [RegisterComponent]
+ public class UnpoweredFlashlightComponent : Component
+ {
+ public override string Name => "UnpoweredFlashlight";
+
+ [DataField("toggleFlashlightSound")]
+ public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
+
+ [ViewVariables] public bool LightOn = false;
+
+ [Verb]
+ public sealed class ToggleFlashlightVerb : Verb
+ {
+ protected override void GetData(IEntity user, UnpoweredFlashlightComponent component, VerbData data)
+ {
+ var canInteract = EntitySystem.Get().CanInteract(user);
+
+ data.Visibility = canInteract ? VerbVisibility.Visible : VerbVisibility.Invisible;
+ data.Text = Loc.GetString("toggle-flashlight-verb-get-data-text");
+ data.IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png";
+ }
+
+ protected override void Activate(IEntity user, UnpoweredFlashlightComponent component)
+ {
+ EntitySystem.Get().ToggleLight(component);
+ }
+ }
+ }
+}
diff --git a/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs b/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs
new file mode 100644
index 0000000000..da42d4c0e3
--- /dev/null
+++ b/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs
@@ -0,0 +1,31 @@
+using Content.Server.Light.Components;
+using Content.Server.Light.Events;
+using Content.Shared.Light;
+using Robust.Server.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Player;
+using System;
+
+namespace Content.Server.Light.EntitySystems
+{
+ public class UnpoweredFlashlightSystem : EntitySystem
+ {
+ public void ToggleLight(UnpoweredFlashlightComponent flashlight)
+ {
+ if (!flashlight.Owner.TryGetComponent(out PointLightComponent? light))
+ return;
+
+ flashlight.LightOn = !flashlight.LightOn;
+ light.Enabled = flashlight.LightOn;
+
+ if (flashlight.Owner.TryGetComponent(out AppearanceComponent? appearance))
+ appearance.SetData(UnpoweredFlashlightVisuals.LightOn, flashlight.LightOn);
+
+ SoundSystem.Play(Filter.Pvs(light.Owner), flashlight.ToggleSound.GetSound(), flashlight.Owner);
+
+ RaiseLocalEvent(flashlight.Owner.Uid, new LightToggleEvent(flashlight.LightOn));
+ }
+
+ }
+}
diff --git a/Content.Server/Light/Events/LightToggleEvent.cs b/Content.Server/Light/Events/LightToggleEvent.cs
new file mode 100644
index 0000000000..d51d89a16a
--- /dev/null
+++ b/Content.Server/Light/Events/LightToggleEvent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameObjects;
+
+namespace Content.Server.Light.Events
+{
+ public class LightToggleEvent : EntityEventArgs
+ {
+ public bool IsOn;
+
+ public LightToggleEvent(bool isOn)
+ {
+ IsOn = isOn;
+ }
+ }
+}
diff --git a/Content.Server/PDA/PDAComponent.cs b/Content.Server/PDA/PDAComponent.cs
index 5adaf73a10..afc9adcc85 100644
--- a/Content.Server/PDA/PDAComponent.cs
+++ b/Content.Server/PDA/PDAComponent.cs
@@ -1,443 +1,44 @@
using System;
-using System.Collections;
using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
using Content.Server.Access.Components;
-using Content.Server.Hands.Components;
-using Content.Server.Items;
-using Content.Server.PDA.Managers;
-using Content.Server.UserInterface;
+using Content.Shared.Containers.ItemSlots;
using Content.Shared.ActionBlocker;
-using Content.Shared.Interaction;
using Content.Shared.PDA;
-using Content.Shared.Popups;
-using Content.Shared.Sound;
-using Content.Shared.Tag;
using Content.Shared.Verbs;
-using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
-using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
using Robust.Shared.Localization;
-using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.PDA
{
[RegisterComponent]
- [ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IAccess))]
- public class PDAComponent : SharedPDAComponent, IInteractUsing, IActivate, IUse, IAccess, IMapInit
+ public class PDAComponent : Component, IAccess
{
- [Dependency] private readonly IPDAUplinkManager _uplinkManager = default!;
- [Dependency] private readonly IEntityManager _entityManager = default!;
+ public override string Name => "PDA";
- [ViewVariables] private ContainerSlot _idSlot = default!;
- [ViewVariables] private ContainerSlot _penSlot = default!;
+ public const string IDSlotName = "pda_id_slot";
+ public const string PenSlotName = "pda_pen_slot";
- [ViewVariables] private bool _lightOn;
+ [ViewVariables] [DataField("idCard")] public string? StartingIdCard;
- [ViewVariables] [DataField("idCard")] private string? _startingIdCard = "AssistantIDCard";
- [ViewVariables] [DataField("pen")] private string? _startingPen = "Pen";
+ [ViewVariables] public IdCardComponent? ContainedID;
+ [ViewVariables] public bool PenInserted;
+ [ViewVariables] public bool FlashlightOn;
- [ViewVariables] public string? OwnerName { get; private set; }
+ [ViewVariables] public string? OwnerName;
- [ViewVariables] public IdCardComponent? ContainedID { get; private set; }
- [ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntity == null;
- [ViewVariables] public bool PenSlotEmpty => _penSlot.ContainedEntity == null;
-
- private UplinkAccount? _syndicateUplinkAccount;
-
- [ViewVariables] public UplinkAccount? SyndicateUplinkAccount => _syndicateUplinkAccount;
-
- [ViewVariables] private readonly PdaAccessSet _accessSet;
-
- [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PDAUiKey.Key);
-
- [DataField("insertIdSound")] private SoundSpecifier _insertIdSound = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/batrifle_magin.ogg");
- [DataField("toggleFlashlightSound")] private SoundSpecifier _toggleFlashlightSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
- [DataField("ejectIdSound")] private SoundSpecifier _ejectIdSound = new SoundPathSpecifier("/Audio/Machines/id_swipe.ogg");
+ // TODO: Move me to ECS after Access refactoring
+ #region Acces Logic
+ [ViewVariables] private readonly PDAAccessSet _accessSet;
public PDAComponent()
{
- _accessSet = new PdaAccessSet(this);
+ _accessSet = new PDAAccessSet(this);
}
- protected override void Initialize()
- {
- base.Initialize();
- _idSlot = ContainerHelpers.EnsureContainer(Owner, "pda_entity_container");
- _penSlot = ContainerHelpers.EnsureContainer(Owner, "pda_pen_slot");
-
- if (UserInterface != null)
- {
- UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
- }
-
- UpdatePDAAppearance();
- }
-
- public void MapInit()
- {
- if (!string.IsNullOrEmpty(_startingIdCard))
- {
- var idCard = _entityManager.SpawnEntity(_startingIdCard, Owner.Transform.Coordinates);
- var idCardComponent = idCard.GetComponent();
- _idSlot.Insert(idCardComponent.Owner);
- ContainedID = idCardComponent;
- }
-
- if (!string.IsNullOrEmpty(_startingPen))
- {
- var pen = _entityManager.SpawnEntity(_startingPen, Owner.Transform.Coordinates);
- _penSlot.Insert(pen);
- }
- }
-
- private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message)
- {
- switch (message.Message)
- {
- case PDARequestUpdateInterfaceMessage _:
- {
- UpdatePDAUserInterface();
- break;
- }
- case PDAToggleFlashlightMessage _:
- {
- ToggleLight();
- break;
- }
-
- case PDAEjectIDMessage _:
- {
- HandleIDEjection(message.Session.AttachedEntity!);
- break;
- }
-
- case PDAEjectPenMessage _:
- {
- HandlePenEjection(message.Session.AttachedEntity!);
- break;
- }
-
- case PDAUplinkBuyListingMessage buyMsg:
- {
- var player = message.Session.AttachedEntity;
- if (player == null) break;
-
- if (!_uplinkManager.TryPurchaseItem(_syndicateUplinkAccount, buyMsg.ItemId,
- player.Transform.Coordinates, out var entity))
- {
- SendNetworkMessage(new PDAUplinkInsufficientFundsMessage(), message.Session.ConnectedClient);
- break;
- }
-
- if (!player.TryGetComponent(out HandsComponent? hands) ||
- !entity.TryGetComponent(out ItemComponent? item))
- break;
-
- hands.PutInHandOrDrop(item);
-
- SendNetworkMessage(new PDAUplinkBuySuccessMessage(), message.Session.ConnectedClient);
- break;
- }
- }
- }
-
- private void UpdatePDAUserInterface()
- {
- var ownerInfo = new PDAIdInfoText
- {
- ActualOwnerName = OwnerName,
- IdOwner = ContainedID?.FullName,
- JobTitle = ContainedID?.JobTitle
- };
-
- //Do we have an account? If so provide the info.
- if (_syndicateUplinkAccount != null)
- {
- var accData = new UplinkAccountData(_syndicateUplinkAccount.AccountHolder,
- _syndicateUplinkAccount.Balance);
- var listings = _uplinkManager.FetchListings.Values.ToArray();
- UserInterface?.SetState(new PDAUpdateState(_lightOn, !PenSlotEmpty, ownerInfo, accData, listings));
- }
- else
- {
- UserInterface?.SetState(new PDAUpdateState(_lightOn, !PenSlotEmpty, ownerInfo));
- }
-
- UpdatePDAAppearance();
- }
-
- private void UpdatePDAAppearance()
- {
- if (Owner.TryGetComponent(out AppearanceComponent? appearance))
- {
- appearance.SetData(PDAVisuals.FlashlightLit, _lightOn);
- appearance.SetData(PDAVisuals.IDCardInserted, !IdSlotEmpty);
- }
- }
-
- private bool TryInsertIdCard(InteractUsingEventArgs eventArgs, IdCardComponent idCardComponent)
- {
- var item = eventArgs.Using;
- if (_idSlot.Contains(item))
- return false;
-
- if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands))
- {
- Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-pda-ui-try-insert-id-card-no-hands"));
- return true;
- }
-
- IEntity? swap = null;
- if (!IdSlotEmpty)
- {
- // Swap
- swap = _idSlot.ContainedEntities[0];
- }
-
- if (!hands.Drop(item))
- {
- return true;
- }
-
- if (swap != null)
- {
- hands.PutInHand(swap.GetComponent());
- }
-
- InsertIdCard(idCardComponent);
-
- UpdatePDAUserInterface();
- return true;
- }
-
- private bool TryInsertPen(InteractUsingEventArgs eventArgs)
- {
- var item = eventArgs.Using;
- if (_penSlot.Contains(item))
- return false;
-
- if (!eventArgs.User.TryGetComponent(out IHandsComponent? hands))
- {
- Owner.PopupMessage(eventArgs.User, Loc.GetString("comp-pda-ui-try-insert-pen-no-hands"));
- return true;
- }
-
- IEntity? swap = null;
- if (!PenSlotEmpty)
- {
- // Swap
- swap = _penSlot.ContainedEntities[0];
- }
-
- if (!hands.Drop(item))
- {
- return true;
- }
-
- if (swap != null)
- {
- hands.PutInHand(swap.GetComponent());
- }
-
- // Insert Pen
- _penSlot.Insert(item);
-
- UpdatePDAUserInterface();
- return true;
- }
-
- async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
- {
- var item = eventArgs.Using;
-
- if (item.TryGetComponent(out var idCardComponent))
- {
- return TryInsertIdCard(eventArgs, idCardComponent);
- }
-
- if (item.HasTag("Write"))
- {
- return TryInsertPen(eventArgs);
- }
-
- return false;
- }
-
- void IActivate.Activate(ActivateEventArgs eventArgs)
- {
- if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
- {
- return;
- }
-
- UserInterface?.Toggle(actor.PlayerSession);
- UpdatePDAAppearance();
- }
-
- bool IUse.UseEntity(UseEntityEventArgs eventArgs)
- {
- if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
- {
- return false;
- }
-
- UserInterface?.Toggle(actor.PlayerSession);
- UpdatePDAAppearance();
- return true;
- }
-
- public void SetPDAOwner(string name)
- {
- OwnerName = name;
- UpdatePDAUserInterface();
- }
-
- public void InsertIdCard(IdCardComponent card)
- {
- _idSlot.Insert(card.Owner);
- ContainedID = card;
- SoundSystem.Play(Filter.Pvs(Owner), _insertIdSound.GetSound(), Owner);
- }
-
- ///
- /// Initialize the PDA's syndicate uplink account.
- ///
- ///
- public void InitUplinkAccount(UplinkAccount acc)
- {
- _syndicateUplinkAccount = acc;
- _uplinkManager.AddNewAccount(_syndicateUplinkAccount);
-
- _syndicateUplinkAccount.BalanceChanged += account =>
- {
- UpdatePDAUserInterface();
- };
-
- UpdatePDAUserInterface();
- }
-
- private void ToggleLight()
- {
- if (!Owner.TryGetComponent(out PointLightComponent? light))
- {
- return;
- }
-
- _lightOn = !_lightOn;
- light.Enabled = _lightOn;
- SoundSystem.Play(Filter.Pvs(Owner), _toggleFlashlightSound.GetSound(), Owner);
- UpdatePDAUserInterface();
- }
-
- private void HandleIDEjection(IEntity pdaUser)
- {
- if (ContainedID == null)
- {
- return;
- }
-
- var cardEntity = ContainedID.Owner;
- _idSlot.Remove(cardEntity);
-
- var hands = pdaUser.GetComponent();
- var cardItemComponent = cardEntity.GetComponent();
- hands.PutInHandOrDrop(cardItemComponent);
- ContainedID = null;
-
- SoundSystem.Play(Filter.Pvs(Owner), _ejectIdSound.GetSound(), Owner);
- UpdatePDAUserInterface();
- }
-
- private void HandlePenEjection(IEntity pdaUser)
- {
- if (PenSlotEmpty)
- return;
-
- var pen = _penSlot.ContainedEntities[0];
- _penSlot.Remove(pen);
-
- var hands = pdaUser.GetComponent();
- var itemComponent = pen.GetComponent();
- hands.PutInHandOrDrop(itemComponent);
-
- UpdatePDAUserInterface();
- }
-
- [Verb]
- public sealed class EjectIDVerb : Verb
- {
- public override bool AlternativeInteraction => true;
-
- protected override void GetData(IEntity user, PDAComponent component, VerbData data)
- {
- if (!EntitySystem.Get().CanInteract(user))
- {
- data.Visibility = VerbVisibility.Invisible;
- return;
- }
-
- data.Text = Loc.GetString("eject-id-verb-get-data-text");
- data.Visibility = component.IdSlotEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible;
- data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png";
- }
-
- protected override void Activate(IEntity user, PDAComponent component)
- {
- component.HandleIDEjection(user);
- }
- }
-
- [Verb]
- public sealed class EjectPenVerb : Verb
- {
- protected override void GetData(IEntity user, PDAComponent component, VerbData data)
- {
- if (!EntitySystem.Get().CanInteract(user))
- {
- data.Visibility = VerbVisibility.Invisible;
- return;
- }
-
- data.Text = Loc.GetString("eject-pen-verb-get-data-text");
- data.Visibility = component.PenSlotEmpty ? VerbVisibility.Invisible : VerbVisibility.Visible;
- data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png";
- }
-
- protected override void Activate(IEntity user, PDAComponent component)
- {
- component.HandlePenEjection(user);
- }
- }
-
- [Verb]
- public sealed class ToggleFlashlightVerb : Verb
- {
- protected override void GetData(IEntity user, PDAComponent component, VerbData data)
- {
- if (!EntitySystem.Get().CanInteract(user))
- {
- data.Visibility = VerbVisibility.Invisible;
- return;
- }
-
- data.Text = Loc.GetString("toggle-flashlight-verb-get-data-text");
- data.IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png";
- }
-
- protected override void Activate(IEntity user, PDAComponent component)
- {
- component.ToggleLight();
- }
- }
-
- private ISet? GetContainedAccess()
+ public ISet? GetContainedAccess()
{
return ContainedID?.Owner?.GetComponent()?.Tags;
}
@@ -450,117 +51,77 @@ namespace Content.Server.PDA
{
throw new NotSupportedException("PDA access list is read-only.");
}
+ #endregion
- private sealed class PdaAccessSet : ISet
+ // TODO: replace me with dynamic verbs for ItemSlotsSystem
+ #region Verbs
+ [Verb]
+ public sealed class EjectPenVerb : Verb
{
- private readonly PDAComponent _pdaComponent;
- private static readonly HashSet EmptySet = new();
-
- public PdaAccessSet(PDAComponent pdaComponent)
+ protected override void GetData(IEntity user, PDAComponent component, VerbData data)
{
- _pdaComponent = pdaComponent;
+ data.Visibility = VerbVisibility.Invisible;
+
+ if (!component.Owner.TryGetComponent(out SharedItemSlotsComponent? slots))
+ return;
+
+ if (!EntitySystem.Get().CanInteract(user))
+ return;
+
+ var item = EntitySystem.Get().PeekItemInSlot(slots, PenSlotName);
+ if (item == null)
+ return;
+
+ data.Visibility = VerbVisibility.Visible;
+ data.Text = Loc.GetString("eject-item-verb-text-default", ("item", item.Name));
+ data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png";
}
- public IEnumerator GetEnumerator()
+ protected override void Activate(IEntity user, PDAComponent pda)
{
- var contained = _pdaComponent.GetContainedAccess() ?? EmptySet;
- return contained.GetEnumerator();
+ var entityManager = pda.Owner.EntityManager;
+ if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
+ {
+ entityManager.EntitySysManager.GetEntitySystem().
+ TryEjectContent(itemSlots, PenSlotName, user);
+ }
}
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- void ICollection.Add(string item)
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- public void ExceptWith(IEnumerable other)
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- public void IntersectWith(IEnumerable other)
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- public bool IsProperSubsetOf(IEnumerable other)
- {
- var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
- return set.IsProperSubsetOf(other);
- }
-
- public bool IsProperSupersetOf(IEnumerable other)
- {
- var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
- return set.IsProperSupersetOf(other);
- }
-
- public bool IsSubsetOf(IEnumerable other)
- {
- var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
- return set.IsSubsetOf(other);
- }
-
- public bool IsSupersetOf(IEnumerable other)
- {
- var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
- return set.IsSupersetOf(other);
- }
-
- public bool Overlaps(IEnumerable other)
- {
- var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
- return set.Overlaps(other);
- }
-
- public bool SetEquals(IEnumerable other)
- {
- var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
- return set.SetEquals(other);
- }
-
- public void SymmetricExceptWith(IEnumerable other)
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- public void UnionWith(IEnumerable other)
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- bool ISet.Add(string item)
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- public void Clear()
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- public bool Contains(string item)
- {
- return _pdaComponent.GetContainedAccess()?.Contains(item) ?? false;
- }
-
- public void CopyTo(string[] array, int arrayIndex)
- {
- var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
- set.CopyTo(array, arrayIndex);
- }
-
- public bool Remove(string item)
- {
- throw new NotSupportedException("PDA access list is read-only.");
- }
-
- public int Count => _pdaComponent.GetContainedAccess()?.Count ?? 0;
- public bool IsReadOnly => true;
}
+
+ [Verb]
+ public sealed class EjectIDVerb : Verb
+ {
+ public override bool AlternativeInteraction => true;
+
+ protected override void GetData(IEntity user, PDAComponent component, VerbData data)
+ {
+ data.Visibility = VerbVisibility.Invisible;
+
+ if (!component.Owner.TryGetComponent(out SharedItemSlotsComponent? slots))
+ return;
+
+ if (!EntitySystem.Get().CanInteract(user))
+ return;
+
+ var item = EntitySystem.Get().PeekItemInSlot(slots, IDSlotName);
+ if (item == null)
+ return;
+
+ data.Visibility = VerbVisibility.Visible;
+ data.Text = Loc.GetString("eject-item-verb-text-default", ("item", item.Name));
+ data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png";
+ }
+
+ protected override void Activate(IEntity user, PDAComponent pda)
+ {
+ var entityManager = pda.Owner.EntityManager;
+ if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
+ {
+ entityManager.EntitySysManager.GetEntitySystem().
+ TryEjectContent(itemSlots, IDSlotName, user);
+ }
+ }
+ }
+ #endregion
}
}
diff --git a/Content.Server/PDA/PDASystem.cs b/Content.Server/PDA/PDASystem.cs
new file mode 100644
index 0000000000..cecc5ef1dc
--- /dev/null
+++ b/Content.Server/PDA/PDASystem.cs
@@ -0,0 +1,183 @@
+using Content.Server.Access.Components;
+using Content.Shared.Containers.ItemSlots;
+using Content.Server.Light.Components;
+using Content.Server.Light.EntitySystems;
+using Content.Server.Light.Events;
+using Content.Server.Traitor.Uplink;
+using Content.Server.Traitor.Uplink.Components;
+using Content.Server.Traitor.Uplink.Systems;
+using Content.Server.UserInterface;
+using Content.Shared.Interaction;
+using Content.Shared.PDA;
+using Robust.Server.GameObjects;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+
+namespace Content.Server.PDA
+{
+ public class PDASystem : EntitySystem
+ {
+ [Dependency] private readonly SharedItemSlotsSystem _slotsSystem = default!;
+ [Dependency] private readonly UplinkSystem _uplinkSystem = default!;
+ [Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnActivateInWorld);
+ SubscribeLocalEvent(OnUse);
+ SubscribeLocalEvent(OnItemSlotChanged);
+ SubscribeLocalEvent(OnLightToggle);
+
+ SubscribeLocalEvent(OnUplinkInit);
+ SubscribeLocalEvent(OnUplinkRemoved);
+ }
+
+ private void OnComponentInit(EntityUid uid, PDAComponent pda, ComponentInit args)
+ {
+ var ui = pda.Owner.GetUIOrNull(PDAUiKey.Key);
+ if (ui != null)
+ ui.OnReceiveMessage += (msg) => OnUIMessage(pda, msg);
+
+ UpdatePDAAppearance(pda);
+ }
+
+ private void OnMapInit(EntityUid uid, PDAComponent pda, MapInitEvent args)
+ {
+ // try to place ID inside item slot
+ if (!string.IsNullOrEmpty(pda.StartingIdCard))
+ {
+ // if pda prototype doesn't have slots, ID will drop down on ground
+ var idCard = EntityManager.SpawnEntity(pda.StartingIdCard, pda.Owner.Transform.Coordinates);
+ if (EntityManager.TryGetComponent(uid, out SharedItemSlotsComponent? itemSlots))
+ _slotsSystem.TryInsertContent(itemSlots, idCard, PDAComponent.IDSlotName);
+ }
+ }
+
+ private void OnUse(EntityUid uid, PDAComponent pda, UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+ args.Handled = OpenUI(pda, args.User);
+ }
+
+ private void OnActivateInWorld(EntityUid uid, PDAComponent pda, ActivateInWorldEvent args)
+ {
+ if (args.Handled)
+ return;
+ args.Handled = OpenUI(pda, args.User);
+ }
+
+ private void OnItemSlotChanged(EntityUid uid, PDAComponent pda, ItemSlotChanged args)
+ {
+ // check if ID slot changed
+ if (args.SlotName == PDAComponent.IDSlotName)
+ {
+ var item = args.ContainedItem;
+ if (item == null || !EntityManager.TryGetComponent(item.Value, out IdCardComponent ? idCard))
+ pda.ContainedID = null;
+ else
+ pda.ContainedID = idCard;
+ }
+ else if (args.SlotName == PDAComponent.PenSlotName)
+ {
+ var item = args.ContainedItem;
+ pda.PenInserted = item != null;
+ }
+
+ UpdatePDAAppearance(pda);
+ UpdatePDAUserInterface(pda);
+ }
+
+ private void OnLightToggle(EntityUid uid, PDAComponent pda, LightToggleEvent args)
+ {
+ pda.FlashlightOn = args.IsOn;
+ UpdatePDAUserInterface(pda);
+ }
+
+ public void SetOwner(PDAComponent pda, string ownerName)
+ {
+ pda.OwnerName = ownerName;
+ UpdatePDAUserInterface(pda);
+ }
+
+ private void OnUplinkInit(EntityUid uid, PDAComponent pda, UplinkInitEvent args)
+ {
+ UpdatePDAUserInterface(pda);
+ }
+
+ private void OnUplinkRemoved(EntityUid uid, PDAComponent pda, UplinkRemovedEvent args)
+ {
+ UpdatePDAUserInterface(pda);
+ }
+
+ private bool OpenUI(PDAComponent pda, IEntity user)
+ {
+ if (!user.TryGetComponent(out ActorComponent? actor))
+ return false;
+
+ var ui = pda.Owner.GetUIOrNull(PDAUiKey.Key);
+ ui?.Toggle(actor.PlayerSession);
+
+ return true;
+ }
+
+ private void UpdatePDAAppearance(PDAComponent pda)
+ {
+ if (pda.Owner.TryGetComponent(out AppearanceComponent? appearance))
+ appearance.SetData(PDAVisuals.IDCardInserted, pda.ContainedID != null);
+ }
+
+ private void UpdatePDAUserInterface(PDAComponent pda)
+ {
+ var ownerInfo = new PDAIdInfoText
+ {
+ ActualOwnerName = pda.OwnerName,
+ IdOwner = pda.ContainedID?.FullName,
+ JobTitle = pda.ContainedID?.JobTitle
+ };
+
+ var hasUplink = pda.Owner.HasComponent();
+
+ var ui = pda.Owner.GetUIOrNull(PDAUiKey.Key);
+ ui?.SetState(new PDAUpdateState(pda.FlashlightOn, pda.PenInserted, ownerInfo, hasUplink));
+ }
+
+ private void OnUIMessage(PDAComponent pda, ServerBoundUserInterfaceMessage msg)
+ {
+ switch (msg.Message)
+ {
+ case PDARequestUpdateInterfaceMessage _:
+ UpdatePDAUserInterface(pda);
+ break;
+ case PDAToggleFlashlightMessage _:
+ {
+ if (pda.Owner.TryGetComponent(out UnpoweredFlashlightComponent? flashlight))
+ _unpoweredFlashlight.ToggleLight(flashlight);
+ break;
+ }
+
+ case PDAEjectIDMessage _:
+ {
+ if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
+ _slotsSystem.TryEjectContent(itemSlots, PDAComponent.IDSlotName, msg.Session.AttachedEntity);
+ break;
+ }
+ case PDAEjectPenMessage _:
+ {
+ if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
+ _slotsSystem.TryEjectContent(itemSlots, PDAComponent.PenSlotName, msg.Session.AttachedEntity);
+ break;
+ }
+ case PDAShowUplinkMessage _:
+ {
+ if (pda.Owner.TryGetComponent(out UplinkComponent? uplink))
+ _uplinkSystem.ToggleUplinkUI(uplink, msg.Session);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Content.Server/PDA/PdaAccessSet.cs b/Content.Server/PDA/PdaAccessSet.cs
new file mode 100644
index 0000000000..bd28b40d23
--- /dev/null
+++ b/Content.Server/PDA/PdaAccessSet.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Content.Server.PDA
+{
+ public sealed class PDAAccessSet : ISet
+ {
+ private readonly PDAComponent _pdaComponent;
+ private static readonly HashSet EmptySet = new();
+
+ public PDAAccessSet(PDAComponent pdaComponent)
+ {
+ _pdaComponent = pdaComponent;
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ var contained = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ return contained.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ void ICollection.Add(string item)
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ public void ExceptWith(IEnumerable other)
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ public void IntersectWith(IEnumerable other)
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ public bool IsProperSubsetOf(IEnumerable other)
+ {
+ var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ return set.IsProperSubsetOf(other);
+ }
+
+ public bool IsProperSupersetOf(IEnumerable other)
+ {
+ var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ return set.IsProperSupersetOf(other);
+ }
+
+ public bool IsSubsetOf(IEnumerable other)
+ {
+ var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ return set.IsSubsetOf(other);
+ }
+
+ public bool IsSupersetOf(IEnumerable other)
+ {
+ var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ return set.IsSupersetOf(other);
+ }
+
+ public bool Overlaps(IEnumerable other)
+ {
+ var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ return set.Overlaps(other);
+ }
+
+ public bool SetEquals(IEnumerable other)
+ {
+ var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ return set.SetEquals(other);
+ }
+
+ public void SymmetricExceptWith(IEnumerable other)
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ public void UnionWith(IEnumerable other)
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ bool ISet.Add(string item)
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ public void Clear()
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ public bool Contains(string item)
+ {
+ return _pdaComponent.GetContainedAccess()?.Contains(item) ?? false;
+ }
+
+ public void CopyTo(string[] array, int arrayIndex)
+ {
+ var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
+ set.CopyTo(array, arrayIndex);
+ }
+
+ public bool Remove(string item)
+ {
+ throw new NotSupportedException("PDA access list is read-only.");
+ }
+
+ public int Count => _pdaComponent.GetContainedAccess()?.Count ?? 0;
+ public bool IsReadOnly => true;
+ }
+}
diff --git a/Content.Server/Sandbox/SandboxManager.cs b/Content.Server/Sandbox/SandboxManager.cs
index 3cead22a89..026f366981 100644
--- a/Content.Server/Sandbox/SandboxManager.cs
+++ b/Content.Server/Sandbox/SandboxManager.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Server.Access.Components;
+using Content.Shared.Containers.ItemSlots;
using Content.Server.GameTicking;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
@@ -133,7 +134,12 @@ namespace Content.Server.Sandbox
{
if (pda.ContainedID == null)
{
- pda.InsertIdCard(CreateFreshId().GetComponent());
+ var newID = CreateFreshId();
+ if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
+ {
+ _entityManager.EntitySysManager.GetEntitySystem().
+ TryInsertContent(itemSlots, newID, PDAComponent.IDSlotName);
+ }
}
else
{
diff --git a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs
new file mode 100644
index 0000000000..9af877e03b
--- /dev/null
+++ b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs
@@ -0,0 +1,93 @@
+using Content.Server.Administration;
+using Content.Server.PDA.Managers;
+using Content.Server.Traitor.Uplink.Components;
+using Content.Server.Traitor.Uplink.Systems;
+using Content.Shared.Administration;
+using Content.Shared.CCVar;
+using Content.Shared.Traitor.Uplink;
+using Robust.Server.Player;
+using Robust.Shared.Configuration;
+using Robust.Shared.Console;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+
+namespace Content.Server.Traitor.Uplink.Commands
+{
+ [AdminCommand(AdminFlags.Fun)]
+ public class AddUplinkCommand : IConsoleCommand
+ {
+ public string Command => "adduplink";
+
+ public string Description => "Creates uplink on selected item and link it to users account";
+
+ public string Help => "Usage: adduplink ";
+
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length < 1)
+ {
+ shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
+ return;
+ }
+
+ // Get player entity
+ if (!IoCManager.Resolve().TryGetSessionByUsername(args[0], out var session))
+ {
+ shell.WriteLine(Loc.GetString("shell-target-player-does-not-exist"));
+ return;
+ }
+ if (session.AttachedEntity == null)
+ {
+ shell.WriteLine(Loc.GetString("Selected player doesn't controll any entity"));
+ return;
+ }
+ var user = session.AttachedEntity;
+
+ // Get target item
+ IEntity? uplinkEntity = null;
+ var entityManager = IoCManager.Resolve();
+ if (args.Length >= 2)
+ {
+ if (!int.TryParse(args[1], out var itemID))
+ {
+ shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number"));
+ return;
+ }
+
+ var eUid = new EntityUid(itemID);
+ if (!eUid.IsValid() || !entityManager.EntityExists(eUid))
+ {
+ shell.WriteLine(Loc.GetString("shell-invalid-entity-id"));
+ return;
+ }
+
+ uplinkEntity = entityManager.GetEntity(eUid);
+ }
+
+ // Get TC count
+ var configManager = IoCManager.Resolve();
+ var tcCount = configManager.GetCVar(CCVars.TraitorStartingBalance);
+
+ // Get account
+ var uplinkManager = IoCManager.Resolve();
+ if (!uplinkManager.TryGetAccount(user.Uid, out UplinkAccount? uplinkAccount))
+ {
+ uplinkAccount = new UplinkAccount(user.Uid, tcCount);
+ if (!uplinkManager.AddNewAccount(uplinkAccount))
+ {
+ shell.WriteLine(Loc.GetString("Can't create new uplink account"));
+ return;
+ }
+ }
+
+ // Finally add uplink
+ if (!entityManager.EntitySysManager.GetEntitySystem()
+ .AddUplink(user, uplinkAccount!, uplinkEntity))
+ {
+ shell.WriteLine(Loc.GetString("Failed to add uplink to the player"));
+ return;
+ }
+ }
+ }
+}
diff --git a/Content.Server/PDA/Managers/IPDAUplinkManager.cs b/Content.Server/Traitor/Uplink/Managers/IUplinkManager.cs
similarity index 81%
rename from Content.Server/PDA/Managers/IPDAUplinkManager.cs
rename to Content.Server/Traitor/Uplink/Managers/IUplinkManager.cs
index 5eb21183e9..ac3f75c932 100644
--- a/Content.Server/PDA/Managers/IPDAUplinkManager.cs
+++ b/Content.Server/Traitor/Uplink/Managers/IUplinkManager.cs
@@ -1,12 +1,13 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.PDA;
+using Content.Shared.Traitor.Uplink;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Server.PDA.Managers
{
- public interface IPDAUplinkManager
+ public interface IUplinkManager
{
public IReadOnlyDictionary FetchListings { get; }
@@ -16,6 +17,8 @@ namespace Content.Server.PDA.Managers
public bool ChangeBalance(UplinkAccount acc, int amt);
+ public bool TryGetAccount(EntityUid owner, out UplinkAccount? acc);
+
public bool TryPurchaseItem(
UplinkAccount? acc,
string itemId,
diff --git a/Content.Server/PDA/Managers/PDAUplinkManager.cs b/Content.Server/Traitor/Uplink/Managers/UplinkManager.cs
similarity index 84%
rename from Content.Server/PDA/Managers/PDAUplinkManager.cs
rename to Content.Server/Traitor/Uplink/Managers/UplinkManager.cs
index 83531adeae..6ca7fc1b5a 100644
--- a/Content.Server/PDA/Managers/PDAUplinkManager.cs
+++ b/Content.Server/Traitor/Uplink/Managers/UplinkManager.cs
@@ -2,19 +2,21 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Server.Mind.Components;
using Content.Shared.PDA;
+using Content.Shared.Traitor.Uplink;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
+using System.Linq;
namespace Content.Server.PDA.Managers
{
- public class PDAUplinkManager : IPDAUplinkManager
+ public class UplinkManager : IUplinkManager
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
- private readonly List _accounts = new();
+ private readonly Dictionary _accounts = new();
private readonly Dictionary _listings = new();
public IReadOnlyDictionary FetchListings => _listings;
@@ -52,18 +54,23 @@ namespace Content.Server.PDA.Managers
return false;
}
- if (_accounts.Contains(acc))
+ if (_accounts.ContainsKey(acc.AccountHolder))
{
return false;
}
- _accounts.Add(acc);
+ _accounts.Add(acc.AccountHolder, acc);
return true;
}
+ public bool TryGetAccount(EntityUid owner, out UplinkAccount? acc)
+ {
+ return _accounts.TryGetValue(owner, out acc);
+ }
+
public bool ChangeBalance(UplinkAccount acc, int amt)
{
- var account = _accounts.Find(uplinkAccount => uplinkAccount.AccountHolder == acc.AccountHolder);
+ var account = _accounts.GetValueOrDefault(acc.AccountHolder);
if (account == null)
{
diff --git a/Content.Server/Traitor/Uplink/UplinkComponent.cs b/Content.Server/Traitor/Uplink/UplinkComponent.cs
new file mode 100644
index 0000000000..b686abb158
--- /dev/null
+++ b/Content.Server/Traitor/Uplink/UplinkComponent.cs
@@ -0,0 +1,24 @@
+using Content.Shared.Sound;
+using Content.Shared.Traitor.Uplink;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Server.Traitor.Uplink.Components
+{
+ [RegisterComponent]
+ public class UplinkComponent : Component
+ {
+ public override string Name => "Uplink";
+
+ [ViewVariables]
+ [DataField("buySuccessSound")]
+ public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg");
+
+ [ViewVariables]
+ [DataField("insufficientFundsSound")]
+ public SoundSpecifier InsufficientFundsSound = new SoundPathSpecifier("/Audio/Effects/error.ogg");
+
+ [ViewVariables] public UplinkAccount? UplinkAccount;
+ }
+}
diff --git a/Content.Server/Traitor/Uplink/UplinkEvents.cs b/Content.Server/Traitor/Uplink/UplinkEvents.cs
new file mode 100644
index 0000000000..12c9d52f07
--- /dev/null
+++ b/Content.Server/Traitor/Uplink/UplinkEvents.cs
@@ -0,0 +1,21 @@
+using Content.Server.Traitor.Uplink.Components;
+using Content.Shared.Traitor.Uplink;
+using Robust.Server.Player;
+using Robust.Shared.GameObjects;
+
+namespace Content.Server.Traitor.Uplink
+{
+ public class UplinkInitEvent : EntityEventArgs
+ {
+ public UplinkComponent Uplink;
+
+ public UplinkInitEvent(UplinkComponent uplink)
+ {
+ Uplink = uplink;
+ }
+ }
+
+ public class UplinkRemovedEvent : EntityEventArgs
+ {
+ }
+}
diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs
new file mode 100644
index 0000000000..370a58d9a4
--- /dev/null
+++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs
@@ -0,0 +1,166 @@
+using Content.Server.Hands.Components;
+using Content.Server.Inventory.Components;
+using Content.Server.Items;
+using Content.Server.PDA;
+using Content.Server.PDA.Managers;
+using Content.Server.Traitor.Uplink.Components;
+using Content.Server.UserInterface;
+using Content.Shared.Traitor.Uplink;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.Audio;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Log;
+using Robust.Shared.Player;
+using System;
+using System.Linq;
+
+namespace Content.Server.Traitor.Uplink.Systems
+{
+ public class UplinkSystem : EntitySystem
+ {
+ [Dependency] private readonly IUplinkManager _uplinkManager = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnRemove);
+ }
+
+ public void SetAccount(UplinkComponent component, UplinkAccount account)
+ {
+ if (component.UplinkAccount != null)
+ {
+ Logger.Error("Can't init one uplink with different account!");
+ return;
+ }
+
+ component.UplinkAccount = account;
+ component.UplinkAccount.BalanceChanged += (acc) =>
+ {
+ UpdateUserInterface(component);
+ };
+
+ }
+
+ private void OnInit(EntityUid uid, UplinkComponent component, ComponentInit args)
+ {
+ var ui = component.Owner.GetUIOrNull(UplinkUiKey.Key);
+ if (ui != null)
+ ui.OnReceiveMessage += (msg) => OnUIMessage(component, msg);
+
+ RaiseLocalEvent(uid, new UplinkInitEvent(component));
+ }
+
+ private void OnRemove(EntityUid uid, UplinkComponent component, ComponentRemove args)
+ {
+ RaiseLocalEvent(uid, new UplinkRemovedEvent());
+ }
+
+ public void ToggleUplinkUI(UplinkComponent component, IPlayerSession session)
+ {
+ var ui = component.Owner.GetUIOrNull(UplinkUiKey.Key);
+ ui?.Toggle(session);
+
+ UpdateUserInterface(component);
+ }
+
+ private void OnUIMessage(UplinkComponent uplink, ServerBoundUserInterfaceMessage message)
+ {
+ switch (message.Message)
+ {
+ case UplinkRequestUpdateInterfaceMessage _:
+ UpdateUserInterface(uplink);
+ break;
+ case UplinkBuyListingMessage buyMsg:
+ {
+ var player = message.Session.AttachedEntity;
+ if (player == null) break;
+
+ if (!_uplinkManager.TryPurchaseItem(uplink.UplinkAccount, buyMsg.ItemId,
+ player.Transform.Coordinates, out var entity))
+ {
+ SoundSystem.Play(Filter.SinglePlayer(message.Session), uplink.InsufficientFundsSound.GetSound(),
+ uplink.Owner, AudioParams.Default);
+ RaiseNetworkEvent(new UplinkInsufficientFundsMessage(), message.Session.ConnectedClient);
+ break;
+ }
+
+ if (player.TryGetComponent(out HandsComponent? hands) &&
+ entity.TryGetComponent(out ItemComponent? item))
+ {
+ hands.PutInHandOrDrop(item);
+ }
+
+ SoundSystem.Play(Filter.SinglePlayer(message.Session), uplink.BuySuccessSound.GetSound(),
+ uplink.Owner, AudioParams.Default.WithVolume(-2f));
+
+ RaiseNetworkEvent(new UplinkBuySuccessMessage(), message.Session.ConnectedClient);
+ break;
+ }
+
+ }
+ }
+
+ private void UpdateUserInterface(UplinkComponent component)
+ {
+ var ui = component.Owner.GetUIOrNull(UplinkUiKey.Key);
+ if (ui == null)
+ return;
+
+ var listings = _uplinkManager.FetchListings.Values.ToArray();
+ var acc = component.UplinkAccount;
+
+ UplinkAccountData accData;
+ if (acc != null)
+ accData = new UplinkAccountData(acc.AccountHolder, acc.Balance);
+ else
+ accData = new UplinkAccountData(null, 0);
+
+ ui.SetState(new UplinkUpdateState(accData, listings));
+ }
+
+ public bool AddUplink(IEntity user, UplinkAccount account, IEntity? uplinkEntity = null)
+ {
+ // Try to find target item
+ if (uplinkEntity == null)
+ {
+ uplinkEntity = FindUplinkTarget(user);
+ if (uplinkEntity == null)
+ return false;
+ }
+
+ var uplink = uplinkEntity.EnsureComponent();
+ SetAccount(uplink, account);
+
+ return true;
+ }
+
+ private IEntity? FindUplinkTarget(IEntity user)
+ {
+ // Try to find PDA in inventory
+ if (user.TryGetComponent(out InventoryComponent? inventory))
+ {
+ var foundPDA = inventory.LookupItems().FirstOrDefault();
+ if (foundPDA != null)
+ return foundPDA.Owner;
+ }
+
+ // Also check hands
+ if (user.TryGetComponent(out IHandsComponent? hands))
+ {
+ var heldItems = hands.GetAllHeldItems();
+ foreach (var item in heldItems)
+ {
+ if (item.Owner.HasComponent())
+ return item.Owner;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Content.Server/TraitorDeathMatch/Components/TraitorDeathMatchRedemptionComponent.cs b/Content.Server/TraitorDeathMatch/Components/TraitorDeathMatchRedemptionComponent.cs
index 07d8c0d8d7..ccba9cd317 100644
--- a/Content.Server/TraitorDeathMatch/Components/TraitorDeathMatchRedemptionComponent.cs
+++ b/Content.Server/TraitorDeathMatch/Components/TraitorDeathMatchRedemptionComponent.cs
@@ -2,6 +2,7 @@ using System.Threading.Tasks;
using Content.Server.Inventory.Components;
using Content.Server.Mind.Components;
using Content.Server.PDA;
+using Content.Server.Traitor.Uplink.Components;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Shared.Popups;
@@ -40,7 +41,7 @@ namespace Content.Server.TraitorDeathMatch.Components
return false;
}
- if (!eventArgs.Using.TryGetComponent(out var victimPDA))
+ if (!eventArgs.Using.TryGetComponent(out var victimUplink))
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("traitor-death-match-redemption-component-interact-using-main-message",
("secondMessage", Loc.GetString("traitor-death-match-redemption-component-interact-using-no-pda-message"))));
@@ -62,13 +63,13 @@ namespace Content.Server.TraitorDeathMatch.Components
}
var userPDAEntity = userInv.GetSlotItem(EquipmentSlotDefines.Slots.IDCARD)?.Owner;
- PDAComponent? userPDA = null;
+ UplinkComponent? userUplink = null;
if (userPDAEntity != null)
- if (userPDAEntity.TryGetComponent(out var userPDAComponent))
- userPDA = userPDAComponent;
+ if (userPDAEntity.TryGetComponent(out var userUplinkComponent))
+ userUplink = userUplinkComponent;
- if (userPDA == null)
+ if (userUplink == null)
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("traitor-death-match-redemption-component-interact-using-main-message",
("secondMessage", Loc.GetString("traitor-death-match-redemption-component-interact-using-no-pda-in-pocket-message"))));
@@ -77,8 +78,8 @@ namespace Content.Server.TraitorDeathMatch.Components
// We have finally determined both PDA components. FINALLY.
- var userAccount = userPDA.SyndicateUplinkAccount;
- var victimAccount = victimPDA.SyndicateUplinkAccount;
+ var userAccount = userUplink.UplinkAccount;
+ var victimAccount = victimUplink.UplinkAccount;
if (userAccount == null)
{
@@ -107,7 +108,7 @@ namespace Content.Server.TraitorDeathMatch.Components
victimAccount.ModifyAccountBalance(0);
userAccount.ModifyAccountBalance(userAccount.Balance + transferAmount);
- victimPDA.Owner.Delete();
+ victimUplink.Owner.Delete();
Owner.PopupMessage(eventArgs.User, Loc.GetString("traitor-death-match-redemption-component-interact-using-success-message", ("tcAmount", transferAmount)));
return true;
diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotEvents.cs b/Content.Shared/Containers/ItemSlot/ItemSlotEvents.cs
new file mode 100644
index 0000000000..1021872d12
--- /dev/null
+++ b/Content.Shared/Containers/ItemSlot/ItemSlotEvents.cs
@@ -0,0 +1,23 @@
+using Robust.Shared.GameObjects;
+
+namespace Content.Shared.Containers.ItemSlots
+{
+ ///
+ /// Item was placed in or removed from one of the slots in
+ ///
+ public class ItemSlotChanged : EntityEventArgs
+ {
+ public SharedItemSlotsComponent SlotsComponent;
+ public string SlotName;
+ public ItemSlot Slot;
+ public readonly EntityUid? ContainedItem;
+
+ public ItemSlotChanged(SharedItemSlotsComponent slotsComponent, string slotName, ItemSlot slot)
+ {
+ SlotsComponent = slotsComponent;
+ SlotName = slotName;
+ Slot = slot;
+ ContainedItem = slot.ContainerSlot.ContainedEntity?.Uid;
+ }
+ }
+}
diff --git a/Content.Shared/Containers/ItemSlot/SharedItemSlotsComponent.cs b/Content.Shared/Containers/ItemSlot/SharedItemSlotsComponent.cs
new file mode 100644
index 0000000000..44f54e59ee
--- /dev/null
+++ b/Content.Shared/Containers/ItemSlot/SharedItemSlotsComponent.cs
@@ -0,0 +1,39 @@
+using Content.Shared.Sound;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.ViewVariables;
+using System;
+using System.Collections.Generic;
+
+namespace Content.Shared.Containers.ItemSlots
+{
+ ///
+ /// Used for entities that can hold items in different slots
+ /// Allows basic insert/eject interaction
+ ///
+ [RegisterComponent]
+ public class SharedItemSlotsComponent : Component
+ {
+ public override string Name => "ItemSlots";
+
+ [ViewVariables] [DataField("slots")] public Dictionary Slots = new();
+ }
+
+ [Serializable]
+ [DataDefinition]
+ public class ItemSlot
+ {
+ [ViewVariables] [DataField("whitelist")] public EntityWhitelist? Whitelist;
+ [ViewVariables] [DataField("insertSound")] public SoundSpecifier? InsertSound;
+ [ViewVariables] [DataField("ejectSound")] public SoundSpecifier? EjectSound;
+
+ [DataField("item", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ [ViewVariables] public string? StartingItem;
+
+ [ViewVariables] public ContainerSlot ContainerSlot = default!;
+ }
+}
diff --git a/Content.Shared/Containers/ItemSlot/SharedItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/SharedItemSlotsSystem.cs
new file mode 100644
index 0000000000..820bb3e175
--- /dev/null
+++ b/Content.Shared/Containers/ItemSlot/SharedItemSlotsSystem.cs
@@ -0,0 +1,187 @@
+using Content.Shared.Hands.Components;
+using Content.Shared.Interaction;
+using Content.Shared.Item;
+using Content.Shared.Popups;
+using Robust.Shared.Audio;
+using Robust.Shared.Containers;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Localization;
+using Robust.Shared.Player;
+
+namespace Content.Shared.Containers.ItemSlots
+{
+ public class SharedItemSlotsSystem : EntitySystem
+ {
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnInteractUsing);
+ }
+
+ private void OnComponentInit(EntityUid uid, SharedItemSlotsComponent itemSlots, ComponentInit args)
+ {
+ // create container for each slot
+ foreach (var pair in itemSlots.Slots)
+ {
+ var slotName = pair.Key;
+ var slot = pair.Value;
+
+ slot.ContainerSlot = ContainerHelpers.EnsureContainer(itemSlots.Owner, slotName);
+ }
+ }
+
+ private void OnMapInit(EntityUid uid, SharedItemSlotsComponent itemSlots, MapInitEvent args)
+ {
+ foreach (var pair in itemSlots.Slots)
+ {
+ var slot = pair.Value;
+ var slotName = pair.Key;
+
+ // Check if someone already put item inside container
+ if (slot.ContainerSlot.ContainedEntity != null)
+ continue;
+
+ // Try to spawn item inside each slot
+ if (!string.IsNullOrEmpty(slot.StartingItem))
+ {
+ var item = EntityManager.SpawnEntity(slot.StartingItem, itemSlots.Owner.Transform.Coordinates);
+ slot.ContainerSlot.Insert(item);
+
+ RaiseLocalEvent(uid, new ItemSlotChanged(itemSlots, slotName, slot));
+ }
+ }
+ }
+
+ private void OnInteractUsing(EntityUid uid, SharedItemSlotsComponent itemSlots, InteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = TryInsertContent(itemSlots, args.Used, args.User);
+ }
+
+ ///
+ /// Tries to insert item in any fitting item slot from users hand
+ ///
+ /// False if failed to insert item
+ public bool TryInsertContent(SharedItemSlotsComponent itemSlots, IEntity item, IEntity user)
+ {
+ foreach (var pair in itemSlots.Slots)
+ {
+ var slotName = pair.Key;
+ var slot = pair.Value;
+
+ // check if item allowed in whitelist
+ if (slot.Whitelist != null && !slot.Whitelist.IsValid(item))
+ continue;
+
+ // check if slot is empty
+ if (slot.ContainerSlot.Contains(item))
+ continue;
+
+ if (!user.TryGetComponent(out SharedHandsComponent? hands))
+ {
+ itemSlots.Owner.PopupMessage(user, Loc.GetString("item-slots-try-insert-no-hands"));
+ return true;
+ }
+
+ // get item inside container
+ IEntity? swap = null;
+ if (slot.ContainerSlot.ContainedEntity != null)
+ swap = slot.ContainerSlot.ContainedEntity;
+
+ // return if user can't drop active item in hand
+ if (!hands.TryDropEntityToFloor(item))
+ return true;
+
+ // swap item in hand and item in slot
+ if (swap != null)
+ hands.TryPutInAnyHand(swap);
+
+ // insert item
+ slot.ContainerSlot.Insert(item);
+ RaiseLocalEvent(itemSlots.Owner.Uid, new ItemSlotChanged(itemSlots, slotName, slot));
+
+ // play sound
+ if (slot.InsertSound != null)
+ SoundSystem.Play(Filter.Pvs(itemSlots.Owner), slot.InsertSound.GetSound(), itemSlots.Owner);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Tries to insert item in known slot. Doesn't interact with user
+ ///
+ /// False if failed to insert item
+ public bool TryInsertContent(SharedItemSlotsComponent itemSlots, IEntity item, string slotName)
+ {
+ if (!itemSlots.Slots.TryGetValue(slotName, out var slot))
+ return false;
+
+ if (slot.ContainerSlot.ContainedEntity != null)
+ return false;
+
+ // check if item allowed in whitelist
+ if (slot.Whitelist != null && !slot.Whitelist.IsValid(item))
+ return false;
+
+ slot.ContainerSlot.Insert(item);
+ RaiseLocalEvent(itemSlots.Owner.Uid, new ItemSlotChanged(itemSlots, slotName, slot));
+ return true;
+ }
+
+ ///
+ /// Check if slot has some content in it (without ejecting item)
+ ///
+ /// Null if doesn't have any content
+ public IEntity? PeekItemInSlot(SharedItemSlotsComponent itemSlots, string slotName)
+ {
+ if (!itemSlots.Slots.TryGetValue(slotName, out var slot))
+ return null;
+
+ var item = slot.ContainerSlot.ContainedEntity;
+ return item;
+ }
+
+ ///
+ /// Try to eject item from slot to users hands
+ ///
+ public bool TryEjectContent(SharedItemSlotsComponent itemSlots, string slotName, IEntity? user)
+ {
+ if (!itemSlots.Slots.TryGetValue(slotName, out var slot))
+ return false;
+
+ if (slot.ContainerSlot.ContainedEntity == null)
+ return false;
+
+ var item = slot.ContainerSlot.ContainedEntity;
+ if (!slot.ContainerSlot.Remove(item))
+ return false;
+
+ // try eject item to users hand
+ if (user != null)
+ {
+ if (user.TryGetComponent(out SharedHandsComponent? hands))
+ {
+ hands.TryPutInAnyHand(item);
+ }
+ else
+ {
+ itemSlots.Owner.PopupMessage(user, Loc.GetString("item-slots-try-insert-no-hands"));
+ }
+ }
+
+ if (slot.EjectSound != null)
+ SoundSystem.Play(Filter.Pvs(itemSlots.Owner), slot.EjectSound.GetSound(), itemSlots.Owner);
+
+ RaiseLocalEvent(itemSlots.Owner.Uid, new ItemSlotChanged(itemSlots, slotName, slot));
+ return true;
+ }
+ }
+}
diff --git a/Content.Shared/Light/SharedUnpoweredFlashlightVisuals.cs b/Content.Shared/Light/SharedUnpoweredFlashlightVisuals.cs
new file mode 100644
index 0000000000..6888cb65af
--- /dev/null
+++ b/Content.Shared/Light/SharedUnpoweredFlashlightVisuals.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.Light
+{
+ [Serializable, NetSerializable]
+ public enum UnpoweredFlashlightVisuals : byte
+ {
+ LightOn
+ }
+}
diff --git a/Content.Shared/PDA/PDAMessagesUI.cs b/Content.Shared/PDA/PDAMessagesUI.cs
new file mode 100644
index 0000000000..5f0e0cb749
--- /dev/null
+++ b/Content.Shared/PDA/PDAMessagesUI.cs
@@ -0,0 +1,52 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.PDA
+{
+ [Serializable, NetSerializable]
+ public sealed class PDAToggleFlashlightMessage : BoundUserInterfaceMessage
+ {
+ public PDAToggleFlashlightMessage()
+ {
+
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class PDAEjectIDMessage : BoundUserInterfaceMessage
+ {
+ public PDAEjectIDMessage()
+ {
+
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class PDAEjectPenMessage : BoundUserInterfaceMessage
+ {
+ public PDAEjectPenMessage()
+ {
+
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class PDAShowUplinkMessage : BoundUserInterfaceMessage
+ {
+ public PDAShowUplinkMessage()
+ {
+
+ }
+ }
+
+
+ [Serializable, NetSerializable]
+ public sealed class PDARequestUpdateInterfaceMessage : BoundUserInterfaceMessage
+ {
+ public PDARequestUpdateInterfaceMessage()
+ {
+
+ }
+ }
+}
diff --git a/Content.Shared/PDA/PDAUpdateState.cs b/Content.Shared/PDA/PDAUpdateState.cs
new file mode 100644
index 0000000000..49715cb2ed
--- /dev/null
+++ b/Content.Shared/PDA/PDAUpdateState.cs
@@ -0,0 +1,33 @@
+using Content.Shared.Traitor.Uplink;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+
+
+namespace Content.Shared.PDA
+{
+ [Serializable, NetSerializable]
+ public sealed class PDAUpdateState : BoundUserInterfaceState
+ {
+ public bool FlashlightEnabled;
+ public bool HasPen;
+ public PDAIdInfoText PDAOwnerInfo;
+ public bool HasUplink;
+
+ public PDAUpdateState(bool flashlightEnabled, bool hasPen, PDAIdInfoText pDAOwnerInfo, bool hasUplink = false)
+ {
+ FlashlightEnabled = flashlightEnabled;
+ HasPen = hasPen;
+ PDAOwnerInfo = pDAOwnerInfo;
+ HasUplink = hasUplink;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public struct PDAIdInfoText
+ {
+ public string? ActualOwnerName;
+ public string? IdOwner;
+ public string? JobTitle;
+ }
+}
diff --git a/Content.Shared/PDA/PDAVisuals.cs b/Content.Shared/PDA/PDAVisuals.cs
new file mode 100644
index 0000000000..7036044ae4
--- /dev/null
+++ b/Content.Shared/PDA/PDAVisuals.cs
@@ -0,0 +1,18 @@
+using System;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.PDA
+{
+ [Serializable, NetSerializable]
+ public enum PDAVisuals
+ {
+ IDCardInserted
+ }
+
+ [Serializable, NetSerializable]
+ public enum PDAUiKey
+ {
+ Key
+ }
+
+}
diff --git a/Content.Shared/PDA/SharedPDAComponent.cs b/Content.Shared/PDA/SharedPDAComponent.cs
deleted file mode 100644
index 307b124b60..0000000000
--- a/Content.Shared/PDA/SharedPDAComponent.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-using System;
-using Robust.Shared.GameObjects;
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-using Robust.Shared.ViewVariables;
-
-namespace Content.Shared.PDA
-{
- [NetworkedComponent()]
- public class SharedPDAComponent : Component
- {
- public override string Name => "PDA";
- }
-
- [Serializable, NetSerializable]
- public sealed class PDAToggleFlashlightMessage : BoundUserInterfaceMessage
- {
- public PDAToggleFlashlightMessage()
- {
-
- }
- }
-
- [Serializable, NetSerializable]
- public sealed class PDAEjectIDMessage : BoundUserInterfaceMessage
- {
- public PDAEjectIDMessage()
- {
-
- }
- }
-
- [Serializable, NetSerializable]
- public sealed class PDAEjectPenMessage : BoundUserInterfaceMessage
- {
- public PDAEjectPenMessage()
- {
-
- }
- }
-
- [Serializable, NetSerializable]
- public class PDAUBoundUserInterfaceState : BoundUserInterfaceState
- {
-
- }
-
- [Serializable, NetSerializable]
- public sealed class PDAUpdateState : PDAUBoundUserInterfaceState
- {
- public bool FlashlightEnabled;
- public bool HasPen;
- public PDAIdInfoText PDAOwnerInfo;
- public UplinkAccountData Account = default!;
- public UplinkListingData[] Listings = default!;
-
- public PDAUpdateState(bool isFlashlightOn, bool hasPen, PDAIdInfoText ownerInfo)
- {
- FlashlightEnabled = isFlashlightOn;
- HasPen = hasPen;
- PDAOwnerInfo = ownerInfo;
- }
-
- public PDAUpdateState(bool isFlashlightOn, bool hasPen, PDAIdInfoText ownerInfo, UplinkAccountData accountData)
- : this(isFlashlightOn, hasPen, ownerInfo)
- {
- Account = accountData;
- }
-
- public PDAUpdateState(bool isFlashlightOn, bool hasPen, PDAIdInfoText ownerInfo, UplinkAccountData accountData, UplinkListingData[] listings)
- : this(isFlashlightOn, hasPen, ownerInfo, accountData)
- {
- Listings = listings;
- }
- }
-
- [Serializable, NetSerializable]
- public sealed class PDAUplinkBuyListingMessage : BoundUserInterfaceMessage
- {
- public string ItemId;
-
- public PDAUplinkBuyListingMessage(string itemId)
- {
- ItemId = itemId;
- }
- }
-
- [Serializable, NetSerializable]
- public sealed class PDAUplinkBuySuccessMessage : ComponentMessage
- {
- }
-
- [Serializable, NetSerializable]
- public sealed class PDAUplinkInsufficientFundsMessage : ComponentMessage
- {
- }
-
- [Serializable, NetSerializable]
- public sealed class PDARequestUpdateInterfaceMessage : BoundUserInterfaceMessage
- {
- public PDARequestUpdateInterfaceMessage()
- {
-
- }
- }
-
- [Serializable, NetSerializable]
- public struct PDAIdInfoText
- {
- public string? ActualOwnerName;
- public string? IdOwner;
- public string? JobTitle;
- }
-
- [Serializable, NetSerializable]
- public enum PDAVisuals
- {
- FlashlightLit,
- IDCardInserted
- }
-
- [Serializable, NetSerializable]
- public enum PDAUiKey
- {
- Key
- }
-
- public class UplinkAccount
- {
- public event Action? BalanceChanged;
- public EntityUid AccountHolder;
- private int _balance;
- [ViewVariables]
- public int Balance => _balance;
-
- public UplinkAccount(EntityUid uid, int startingBalance)
- {
- AccountHolder = uid;
- _balance = startingBalance;
- }
-
- public bool ModifyAccountBalance(int newBalance)
- {
- if (newBalance < 0)
- {
- return false;
- }
- _balance = newBalance;
- BalanceChanged?.Invoke(this);
- return true;
- }
- }
-
- [Serializable, NetSerializable]
- public class UplinkAccountData
- {
- public EntityUid DataAccountHolder;
- public int DataBalance;
-
- public UplinkAccountData(EntityUid dataAccountHolder, int dataBalance)
- {
- DataAccountHolder = dataAccountHolder;
- DataBalance = dataBalance;
- }
- }
-
- [Serializable, NetSerializable]
- public class UplinkListingData : ComponentState, IEquatable
- {
- public string ItemId;
- public int Price;
- public UplinkCategory Category;
- public string Description;
- public string ListingName;
-
- public UplinkListingData(string listingName,string itemId,
- int price, UplinkCategory category,
- string description)
- {
- ListingName = listingName;
- Price = price;
- Category = category;
- Description = description;
- ItemId = itemId;
- }
-
- public bool Equals(UplinkListingData? other)
- {
- if (other == null)
- {
- return false;
- }
-
- return ItemId == other.ItemId;
- }
- }
-}
diff --git a/Content.Shared/Traitor/Uplink/UplinkAccount.cs b/Content.Shared/Traitor/Uplink/UplinkAccount.cs
new file mode 100644
index 0000000000..a4dddd836a
--- /dev/null
+++ b/Content.Shared/Traitor/Uplink/UplinkAccount.cs
@@ -0,0 +1,32 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.ViewVariables;
+using System;
+
+namespace Content.Shared.Traitor.Uplink
+{
+ public class UplinkAccount
+ {
+ public event Action? BalanceChanged;
+ public EntityUid AccountHolder;
+ private int _balance;
+ [ViewVariables]
+ public int Balance => _balance;
+
+ public UplinkAccount(EntityUid uid, int startingBalance)
+ {
+ AccountHolder = uid;
+ _balance = startingBalance;
+ }
+
+ public bool ModifyAccountBalance(int newBalance)
+ {
+ if (newBalance < 0)
+ {
+ return false;
+ }
+ _balance = newBalance;
+ BalanceChanged?.Invoke(this);
+ return true;
+ }
+ }
+}
diff --git a/Content.Shared/Traitor/Uplink/UplinkAccountData.cs b/Content.Shared/Traitor/Uplink/UplinkAccountData.cs
new file mode 100644
index 0000000000..534fbb1002
--- /dev/null
+++ b/Content.Shared/Traitor/Uplink/UplinkAccountData.cs
@@ -0,0 +1,19 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.Traitor.Uplink
+{
+ [Serializable, NetSerializable]
+ public class UplinkAccountData
+ {
+ public EntityUid? DataAccountHolder;
+ public int DataBalance;
+
+ public UplinkAccountData(EntityUid? dataAccountHolder, int dataBalance)
+ {
+ DataAccountHolder = dataAccountHolder;
+ DataBalance = dataBalance;
+ }
+ }
+}
diff --git a/Content.Shared/Traitor/Uplink/UplinkListingData.cs b/Content.Shared/Traitor/Uplink/UplinkListingData.cs
new file mode 100644
index 0000000000..828a3750d1
--- /dev/null
+++ b/Content.Shared/Traitor/Uplink/UplinkListingData.cs
@@ -0,0 +1,38 @@
+using Content.Shared.PDA;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.Traitor.Uplink
+{
+ [Serializable, NetSerializable]
+ public class UplinkListingData : ComponentState, IEquatable
+ {
+ public string ItemId;
+ public int Price;
+ public UplinkCategory Category;
+ public string Description;
+ public string ListingName;
+
+ public UplinkListingData(string listingName, string itemId,
+ int price, UplinkCategory category,
+ string description)
+ {
+ ListingName = listingName;
+ Price = price;
+ Category = category;
+ Description = description;
+ ItemId = itemId;
+ }
+
+ public bool Equals(UplinkListingData? other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ return ItemId == other.ItemId;
+ }
+ }
+}
diff --git a/Content.Shared/Traitor/Uplink/UplinkMessagesUI.cs b/Content.Shared/Traitor/Uplink/UplinkMessagesUI.cs
new file mode 100644
index 0000000000..3a036ca6b5
--- /dev/null
+++ b/Content.Shared/Traitor/Uplink/UplinkMessagesUI.cs
@@ -0,0 +1,26 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.Traitor.Uplink
+{
+ [Serializable, NetSerializable]
+ public sealed class UplinkBuyListingMessage : BoundUserInterfaceMessage
+ {
+ public string ItemId;
+
+ public UplinkBuyListingMessage(string itemId)
+ {
+ ItemId = itemId;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class UplinkRequestUpdateInterfaceMessage : BoundUserInterfaceMessage
+ {
+ public UplinkRequestUpdateInterfaceMessage()
+ {
+
+ }
+ }
+}
diff --git a/Content.Shared/Traitor/Uplink/UplinkNetworkEvents.cs b/Content.Shared/Traitor/Uplink/UplinkNetworkEvents.cs
new file mode 100644
index 0000000000..88b7f2feda
--- /dev/null
+++ b/Content.Shared/Traitor/Uplink/UplinkNetworkEvents.cs
@@ -0,0 +1,16 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.Traitor.Uplink
+{
+ [Serializable, NetSerializable]
+ public sealed class UplinkBuySuccessMessage : EntityEventArgs
+ {
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class UplinkInsufficientFundsMessage : EntityEventArgs
+ {
+ }
+}
diff --git a/Content.Shared/Traitor/Uplink/UplinkUpdateState.cs b/Content.Shared/Traitor/Uplink/UplinkUpdateState.cs
new file mode 100644
index 0000000000..496d4e1d9c
--- /dev/null
+++ b/Content.Shared/Traitor/Uplink/UplinkUpdateState.cs
@@ -0,0 +1,19 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.Traitor.Uplink
+{
+ [Serializable, NetSerializable]
+ public class UplinkUpdateState : BoundUserInterfaceState
+ {
+ public UplinkAccountData Account;
+ public UplinkListingData[] Listings;
+
+ public UplinkUpdateState(UplinkAccountData account, UplinkListingData[] listings)
+ {
+ Account = account;
+ Listings = listings;
+ }
+ }
+}
diff --git a/Content.Shared/Traitor/Uplink/UplinkVisuals.cs b/Content.Shared/Traitor/Uplink/UplinkVisuals.cs
new file mode 100644
index 0000000000..4d5204cd52
--- /dev/null
+++ b/Content.Shared/Traitor/Uplink/UplinkVisuals.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.Traitor.Uplink
+{
+ [Serializable, NetSerializable]
+ public enum UplinkUiKey : byte
+ {
+ Key
+ }
+}
diff --git a/Resources/Locale/en-US/containers/item-slots-component.ftl b/Resources/Locale/en-US/containers/item-slots-component.ftl
new file mode 100644
index 0000000000..77aca397d3
--- /dev/null
+++ b/Resources/Locale/en-US/containers/item-slots-component.ftl
@@ -0,0 +1,4 @@
+item-slots-try-insert-no-hands = You have no hands.
+
+# EjectItemVerb
+eject-item-verb-text-default = Eject {$item}
diff --git a/Resources/Locale/en-US/light/components/unpowered-flashlight-component.ftl b/Resources/Locale/en-US/light/components/unpowered-flashlight-component.ftl
new file mode 100644
index 0000000000..782eed8364
--- /dev/null
+++ b/Resources/Locale/en-US/light/components/unpowered-flashlight-component.ftl
@@ -0,0 +1,2 @@
+# ToggleFlashlightVerb
+toggle-flashlight-verb-get-data-text = Toggle flashlight
\ No newline at end of file
diff --git a/Resources/Locale/en-US/pda/pda-component.ftl b/Resources/Locale/en-US/pda/pda-component.ftl
index e741b3ef63..71fb638fcf 100644
--- a/Resources/Locale/en-US/pda/pda-component.ftl
+++ b/Resources/Locale/en-US/pda/pda-component.ftl
@@ -1,9 +1,6 @@
### UI
-comp-pda-ui-try-insert-id-card-no-hands = You have no hands.
-comp-pda-ui-try-insert-pen-no-hands = You have no hands.
-
# For the PDA screen
comp-pda-ui = ID: [color=white]{$Owner}[/color], [color=yellow]{$JobTitle}[/color]
@@ -11,10 +8,6 @@ comp-pda-ui-blank = ID:
comp-pda-ui-owner = Owner: [color=white]{$ActualOwnerName}[/color]
-pda-bound-user-interface-insufficient-funds-popup = Insufficient funds!
-
-pda-bound-user-interface-tc-balance-popup = TC Balance: [color={$weightedColor}]{$balance}[/color]
-
pda-bound-user-interface-main-menu-tab-title = Main Menu
pda-bound-user-interface-uplink-tab-title = Uplink
@@ -26,12 +19,3 @@ comp-pda-ui-eject-id-button = Eject ID
comp-pda-ui-eject-pen-button = Eject Pen
comp-pda-ui-toggle-flashlight-button = Toggle Flashlight
-
-# EjectIDVerb
-eject-id-verb-get-data-text = Eject ID
-
-# EjectPenVerb
-eject-pen-verb-get-data-text = Eject Pen
-
-# ToggleFlashlightVerb
-toggle-flashlight-verb-get-data-text = Toggle flashlight
\ No newline at end of file
diff --git a/Resources/Locale/en-US/traitor/uplink/uplink-component.ftl b/Resources/Locale/en-US/traitor/uplink/uplink-component.ftl
new file mode 100644
index 0000000000..61be32ed66
--- /dev/null
+++ b/Resources/Locale/en-US/traitor/uplink/uplink-component.ftl
@@ -0,0 +1,3 @@
+uplink-bound-user-interface-insufficient-funds-popup = Insufficient funds!
+
+uplink-bound-user-interface-tc-balance-popup = TC Balance: [color={$weightedColor}]{$balance}[/color]
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index 25b4119e82..215a291277 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
@@ -21,6 +21,7 @@
Slots:
- idcard
- Belt
+ - type: UnpoweredFlashlight
- type: PointLight
enabled: false
radius: 1.4
@@ -30,6 +31,24 @@
interfaces:
- key: enum.PDAUiKey.Key
type: PDABoundUserInterface
+ - key: enum.UplinkUiKey.Key
+ type: UplinkBoundUserInterface
+ - type: ItemSlots
+ slots:
+ pda_pen_slot:
+ item: "Pen"
+ whitelist:
+ tags:
+ - Write
+ pda_id_slot:
+ insertSound:
+ path: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
+ ejectSound:
+ path: /Audio/Machines/id_swipe.ogg
+ whitelist:
+ components:
+ - IdCard
+ - type: PDA
- type: DoorBumpOpener
- type: entity