Moving PDA to ECS (#4538)

* Moved pen slot to separate component

* Moved it all to more generic item slot class

* Add sounds

* Item slots now supports many slots

* Some clean-up

* Refactored slots a bit

* Moving ID card out

* Moving pda to system

* Moving PDA owner to ECS

* Moved PDA flashlight to separate component

* Toggle lights work through events

* Fixing UI

* Moving uplink to separate component

* Continue moving uplink to separate component

* More cleaning

* Removing pda shared

* Nuked shared pda component

* Fixed flashlight

* Pen slot now showed in UI

* Light toggle now shows correctly in UI

* Small refactoring of item slots

* Added contained entity

* Fixed tests

* Finished with PDA

* Moving PDA uplink to separate window

* Adding-removing uplink should show new button

* Working on a better debug

* Debug command to add uplink

* Uplink send state to UI

* Almost working UI

* Uplink correcty updates when you buy-sell items

* Ups

* Moved localization to separate file

* Minor fixes

* Removed item slots methods events

* Removed PDA owner name

* Removed one uplink event

* Deleted all uplink events

* Removed flashlight events

* Update Content.Shared/Traitor/Uplink/UplinkVisuals.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Update Content.Server/Containers/ItemSlot/ItemSlotsSystem.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Update Content.Server/Containers/ItemSlot/ItemSlotsSystem.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Update Content.Server/GameTicking/Presets/PresetTraitorDeathMatch.cs

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Item slots system review

* Flashlight review

* PDA to XAML

* Move UplinkMenu to seperate class, fix WeightedColors methods

* Move UI to XAML

* Moved events to entity id

* Address review

* Removed uplink extensions

* Minor fix

* Moved item slots to shared

* My bad Robust...

* Fixed pda sound

* Fixed pda tests

* Fixed pda test again

Co-authored-by: Alexander Evgrashin <evgrashin.adl@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: Visne <vincefvanwijk@gmail.com>
This commit is contained in:
Alex Evgrashin
2021-10-03 07:05:52 +03:00
committed by GitHub
parent 39270d3ec7
commit e5df8dbee3
50 changed files with 1815 additions and 1313 deletions

View File

@@ -275,6 +275,9 @@ namespace Content.Client.Entry
"Advertise", "Advertise",
"PowerNetworkBattery", "PowerNetworkBattery",
"BatteryCharger", "BatteryCharger",
"UnpoweredFlashlight",
"Uplink",
"PDA",
"SpawnItemsOnUse", "SpawnItemsOnUse",
"Wieldable", "Wieldable",
"IncreaseDamageOnWield", "IncreaseDamageOnWield",

View File

@@ -1,19 +1,13 @@
using System;
using Content.Client.Examine;
using Content.Client.Message; using Content.Client.Message;
using Content.Shared.PDA; using Content.Shared.PDA;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.BoxContainer; using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.PDA namespace Content.Client.PDA
@@ -21,11 +15,7 @@ namespace Content.Client.PDA
[UsedImplicitly] [UsedImplicitly]
public class PDABoundUserInterface : BoundUserInterface public class PDABoundUserInterface : BoundUserInterface
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
private PDAMenu? _menu; private PDAMenu? _menu;
private PDAMenuPopup? _failPopup;
public PDABoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) public PDABoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{ {
@@ -35,7 +25,7 @@ namespace Content.Client.PDA
{ {
base.Open(); base.Open();
SendMessage(new PDARequestUpdateInterfaceMessage()); SendMessage(new PDARequestUpdateInterfaceMessage());
_menu = new PDAMenu(this, _prototypeManager); _menu = new PDAMenu();
_menu.OpenToLeft(); _menu.OpenToLeft();
_menu.OnClose += Close; _menu.OnClose += Close;
_menu.FlashLightToggleButton.OnToggled += _ => _menu.FlashLightToggleButton.OnToggled += _ =>
@@ -43,7 +33,7 @@ namespace Content.Client.PDA
SendMessage(new PDAToggleFlashlightMessage()); SendMessage(new PDAToggleFlashlightMessage());
}; };
_menu.EjectIDButton.OnPressed += _ => _menu.EjectIdButton.OnPressed += _ =>
{ {
SendMessage(new PDAEjectIDMessage()); SendMessage(new PDAEjectIDMessage());
}; };
@@ -53,37 +43,11 @@ namespace Content.Client.PDA
SendMessage(new PDAEjectPenMessage()); SendMessage(new PDAEjectPenMessage());
}; };
_menu.MasterTabContainer.OnTabChanged += i => _menu.ActivateUplinkButton.OnPressed += _ =>
{ {
var tab = _menu.MasterTabContainer.GetChild(i); SendMessage(new PDAShowUplinkMessage());
if (tab == _menu.UplinkTabContainer)
{
SendMessage(new PDARequestUpdateInterfaceMessage());
}
}; };
_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) protected override void UpdateState(BoundUserInterfaceState state)
@@ -103,45 +67,26 @@ namespace Content.Client.PDA
if (msg.PDAOwnerInfo.ActualOwnerName != null) 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))); ("ActualOwnerName", msg.PDAOwnerInfo.ActualOwnerName)));
} }
if (msg.PDAOwnerInfo.IdOwner != null || msg.PDAOwnerInfo.JobTitle != null) 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"), ("Owner",msg.PDAOwnerInfo.IdOwner ?? "Unknown"),
("JobTitle",msg.PDAOwnerInfo.JobTitle ?? "Unassigned"))); ("JobTitle",msg.PDAOwnerInfo.JobTitle ?? "Unassigned")));
} }
else 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.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; break;
} }
} }
@@ -156,410 +101,5 @@ namespace Content.Client.PDA
_menu?.Dispose(); _menu?.Dispose();
} }
/// <summary>
/// This is shitcode. It is, however, "PJB-approved shitcode".
/// </summary>
/// <param name="x"></param>
/// <returns></returns>
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<ButtonEventArgs, UplinkListingData>? OnListingButtonPressed;
public event Action<ButtonEventArgs, UplinkCategory>? 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;
}
}
} }
} }

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,32 @@
<SS14Window xmlns="https://spacestation14.io"
Title="{Loc 'comp-pda-ui-menu-title'}"
MinSize="512 256"
SetSize="512 256">
<TabContainer Name="MasterTabContainer">
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="50 50">
<RichTextLabel Name="PdaOwnerLabelProtected" />
<PanelContainer>
<BoxContainer Orientation="Horizontal">
<RichTextLabel Name="IdInfoLabelProtected"
HorizontalExpand="True" />
<Button Name="EjectIdButtonProtected"
Text="{Loc 'comp-pda-ui-eject-id-button'}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Button Name="EjectPenButtonProtected"
Text="{Loc 'comp-pda-ui-eject-pen-button'}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</BoxContainer>
</PanelContainer>
<Button Name="FlashLightToggleButtonProtected"
Text="{Loc 'comp-pda-ui-toggle-flashlight-button'}"
ToggleMode="True" />
<Button Name="ActivateUplinkButtonProtected"
Text="{Loc 'pda-bound-user-interface-uplink-tab-title'}" />
</BoxContainer>
</TabContainer>
</SS14Window>

View File

@@ -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"));
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.PDA; using Content.Shared.Light;
using Content.Shared.PDA;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -46,9 +47,9 @@ namespace Content.Client.PDA
var sprite = component.Owner.GetComponent<ISpriteComponent>(); var sprite = component.Owner.GetComponent<ISpriteComponent>();
sprite.LayerSetVisible(PDAVisualLayers.Flashlight, false); 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)) if (component.TryGetData(PDAVisuals.IDCardInserted, out bool isCardInserted))

View File

@@ -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 }
});
}
}
}
}

View File

@@ -0,0 +1,43 @@
<SS14Window xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'pda-bound-user-interface-uplink-tab-title'}"
MinSize="512 512"
SetSize="512 512">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Vertical"
VerticalExpand="True">
<RichTextLabel Name="BalanceInfoProtected"
HorizontalAlignment="Center" />
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
</PanelContainer.PanelOverride>
<SplitContainer Orientation="Horizontal"
VerticalExpand="True">
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#80808005" />
</PanelContainer.PanelOverride>
<BoxContainer Name="CategoryListContainer"
Orientation="Vertical">
<!-- Category buttons are added here by code -->
</BoxContainer>
</PanelContainer>
<ScrollContainer HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="2"
MinSize="100 256">
<BoxContainer Name="UplinkListingsContainer"
Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
SizeFlagsStretchRatio="2"
MinSize="100 256">
<!-- Listings are added here by code -->
</BoxContainer>
</ScrollContainer>
</SplitContainer>
</PanelContainer>
</BoxContainer>
</BoxContainer>
</SS14Window>

View File

@@ -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<BaseButton.ButtonEventArgs, UplinkListingData>? OnListingButtonPressed;
public event Action<BaseButton.ButtonEventArgs, UplinkCategory>? 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;
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Access.Components; using Content.Server.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Inventory.Components; using Content.Server.Inventory.Components;
using Content.Server.Items; using Content.Server.Items;
@@ -28,8 +29,13 @@ namespace Content.IntegrationTests.Tests.PDA
id: {PdaDummy} id: {PdaDummy}
name: {PdaDummy} name: {PdaDummy}
components: components:
- type: ItemSlots
slots:
pda_id_slot:
whitelist:
components:
- IdCard
- type: PDA - type: PDA
idCard: {IdCardDummy}
- type: Item"; - type: Item";
[Test] [Test]
@@ -69,13 +75,17 @@ namespace Content.IntegrationTests.Tests.PDA
player.GetComponent<IHandsComponent>().PutInHand(pdaItemComponent); player.GetComponent<IHandsComponent>().PutInHand(pdaItemComponent);
var pdaComponent = dummyPda.GetComponent<PDAComponent>(); var pdaComponent = dummyPda.GetComponent<PDAComponent>();
var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, player.Transform.MapPosition).GetComponent<IdCardComponent>(); var pdaIdCard = sEntityManager.SpawnEntity(IdCardDummy, player.Transform.MapPosition);
pdaComponent.InsertIdCard(pdaIdCard);
var itemSlots = dummyPda.GetComponent<SharedItemSlotsComponent>();
sEntityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>()
.TryInsertContent(itemSlots, pdaIdCard, PDAComponent.IDSlotName);
var pdaContainedId = pdaComponent.ContainedID; var pdaContainedId = pdaComponent.ContainedID;
// The PDA in the hand should be found first // The PDA in the hand should be found first
Assert.NotNull(player.GetHeldId()); Assert.NotNull(player.GetHeldId());
Assert.True(player.TryGetHeldId(out id)); Assert.True(player.TryGetHeldId(out id));
Assert.NotNull(id); Assert.NotNull(id);
Assert.That(id, Is.EqualTo(pdaContainedId)); Assert.That(id, Is.EqualTo(pdaContainedId));

View File

@@ -87,7 +87,7 @@ namespace Content.Server.Entry
IoCManager.Resolve<ActionManager>().Initialize(); IoCManager.Resolve<ActionManager>().Initialize();
IoCManager.Resolve<BlackboardManager>().Initialize(); IoCManager.Resolve<BlackboardManager>().Initialize();
IoCManager.Resolve<ConsiderationsManager>().Initialize(); IoCManager.Resolve<ConsiderationsManager>().Initialize();
IoCManager.Resolve<IPDAUplinkManager>().Initialize(); IoCManager.Resolve<IUplinkManager>().Initialize();
IoCManager.Resolve<IAdminManager>().Initialize(); IoCManager.Resolve<IAdminManager>().Initialize();
IoCManager.Resolve<INpcBehaviorManager>().Initialize(); IoCManager.Resolve<INpcBehaviorManager>().Initialize();
IoCManager.Resolve<IAfkManager>().Initialize(); IoCManager.Resolve<IAfkManager>().Initialize();

View File

@@ -234,7 +234,8 @@ namespace Content.Server.GameTicking
var access = card.Owner.GetComponent<AccessComponent>(); var access = card.Owner.GetComponent<AccessComponent>();
var accessTags = access.Tags; var accessTags = access.Tags;
accessTags.UnionWith(jobPrototype.Access); accessTags.UnionWith(jobPrototype.Access);
pdaComponent.SetPDAOwner(characterName); EntityManager.EntitySysManager.GetEntitySystem<PDASystem>()
.SetOwner(pdaComponent, characterName);
} }
#endregion #endregion

View File

@@ -1,17 +1,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules;
using Content.Server.Inventory.Components; using Content.Server.Inventory.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Server.PDA; using Content.Server.PDA.Managers;
using Content.Server.Players; using Content.Server.Players;
using Content.Server.Suspicion; using Content.Server.Suspicion;
using Content.Server.Suspicion.Roles; 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.CCVar;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.PDA;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Traitor.Uplink;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -31,6 +33,8 @@ namespace Content.Server.GameTicking.Presets
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = 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 MinPlayers { get; set; }
public int MinTraitors { get; set; } public int MinTraitors { get; set; }
@@ -109,28 +113,17 @@ namespace Content.Server.GameTicking.Presets
var traitorRole = new SuspicionTraitorRole(mind!, antagPrototype); var traitorRole = new SuspicionTraitorRole(mind!, antagPrototype);
mind!.AddRole(traitorRole); mind!.AddRole(traitorRole);
traitors.Add(traitorRole); traitors.Add(traitorRole);
// creadth: we need to create uplink for the antag. // creadth: we need to create uplink for the antag.
// PDA should be in place already, so we just need to // PDA should be in place already, so we just need to
// initiate uplink account. // initiate uplink account.
var uplinkAccount = var uplinkAccount = new UplinkAccount(mind.OwnedEntity!.Uid, TraitorStartingBalance);
new UplinkAccount(mind.OwnedEntity!.Uid, _uplinkManager.AddNewAccount(uplinkAccount);
TraitorStartingBalance);
var inventory = mind.OwnedEntity.GetComponent<InventoryComponent>(); // try to place uplink
if (!inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent? pdaItem)) if (!EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
{ .AddUplink(mind.OwnedEntity, uplinkAccount))
continue; continue;
}
var pda = pdaItem.Owner;
var pdaComponent = pda.GetComponent<PDAComponent>();
if (pdaComponent.IdSlotEmpty)
{
continue;
}
pdaComponent.InitUplinkAccount(uplinkAccount);
} }
foreach (var player in list) foreach (var player in list)

View File

@@ -7,13 +7,16 @@ using Content.Server.Inventory.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Server.Objectives.Interfaces; using Content.Server.Objectives.Interfaces;
using Content.Server.PDA; using Content.Server.PDA;
using Content.Server.PDA.Managers;
using Content.Server.Players; using Content.Server.Players;
using Content.Server.Traitor; 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.CCVar;
using Content.Shared.Dataset; using Content.Shared.Dataset;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.PDA; using Content.Shared.Traitor.Uplink;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -34,6 +37,8 @@ namespace Content.Server.GameTicking.Presets
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = 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"); public override string ModeTitle => Loc.GetString("traitor-title");
@@ -120,27 +125,15 @@ namespace Content.Server.GameTicking.Presets
DebugTools.AssertNotNull(mind.OwnedEntity); DebugTools.AssertNotNull(mind.OwnedEntity);
var uplinkAccount = new UplinkAccount(mind.OwnedEntity!.Uid, StartingBalance); var uplinkAccount = new UplinkAccount(mind.OwnedEntity!.Uid, StartingBalance);
var inventory = mind.OwnedEntity.GetComponent<InventoryComponent>(); _uplinkManager.AddNewAccount(uplinkAccount);
if (!inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent? pdaItem))
{
Logger.ErrorS("preset", "Failed getting pda for picked traitor.");
continue;
}
var pda = pdaItem.Owner; if (!EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
.AddUplink(mind.OwnedEntity, uplinkAccount))
var pdaComponent = pda.GetComponent<PDAComponent>();
if (pdaComponent.IdSlotEmpty)
{
Logger.ErrorS("preset","PDA had no id for picked traitor");
continue; continue;
}
var traitorRole = new TraitorRole(mind); var traitorRole = new TraitorRole(mind);
mind.AddRole(traitorRole); mind.AddRole(traitorRole);
_traitors.Add(traitorRole); _traitors.Add(traitorRole);
pdaComponent.InitUplinkAccount(uplinkAccount);
} }
var adjectives = _prototypeManager.Index<DatasetPrototype>("adjectives").Values; var adjectives = _prototypeManager.Index<DatasetPrototype>("adjectives").Values;

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules;
@@ -17,7 +16,6 @@ using Content.Shared.CCVar;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.MobState; using Content.Shared.MobState;
using Content.Shared.PDA;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -27,6 +25,11 @@ using Robust.Shared.Localization;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Random; 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; using Content.Shared.Damage.Prototypes;
namespace Content.Server.GameTicking.Presets namespace Content.Server.GameTicking.Presets
@@ -40,6 +43,7 @@ namespace Content.Server.GameTicking.Presets
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUplinkManager _uplinkManager = default!;
public string PDAPrototypeName => "CaptainPDA"; public string PDAPrototypeName => "CaptainPDA";
public string BeltPrototypeName => "ClothingBeltJanitorFilled"; public string BeltPrototypeName => "ClothingBeltJanitorFilled";
@@ -103,12 +107,16 @@ namespace Content.Server.GameTicking.Presets
// Like normal traitors, they need access to a traitor account. // Like normal traitors, they need access to a traitor account.
var uplinkAccount = new UplinkAccount(mind.OwnedEntity.Uid, startingBalance); var uplinkAccount = new UplinkAccount(mind.OwnedEntity.Uid, startingBalance);
var pdaComponent = newPDA.GetComponent<PDAComponent>(); _uplinkManager.AddNewAccount(uplinkAccount);
pdaComponent.InitUplinkAccount(uplinkAccount); _entityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
.AddUplink(mind.OwnedEntity, uplinkAccount, newPDA);
_allOriginalNames[uplinkAccount] = mind.OwnedEntity.Name; _allOriginalNames[uplinkAccount] = mind.OwnedEntity.Name;
// The PDA needs to be marked with the correct owner. // The PDA needs to be marked with the correct owner.
pdaComponent.SetPDAOwner(mind.OwnedEntity.Name); var pda = newPDA.GetComponent<PDAComponent>();
_entityManager.EntitySysManager.GetEntitySystem<PDASystem>()
.SetOwner(pda, mind.OwnedEntity.Name);
newPDA.AddComponent<TraitorDeathMatchReliableOwnerTagComponent>().UserId = mind.UserId; newPDA.AddComponent<TraitorDeathMatchReliableOwnerTagComponent>().UserId = mind.UserId;
} }
@@ -217,14 +225,14 @@ namespace Content.Server.GameTicking.Presets
{ {
var lines = new List<string>(); var lines = new List<string>();
lines.Add("traitor-death-match-end-round-description-first-line"); lines.Add("traitor-death-match-end-round-description-first-line");
foreach (var pda in _entityManager.EntityQuery<PDAComponent>()) foreach (var uplink in _entityManager.EntityQuery<UplinkComponent>(true))
{ {
var uplink = pda.SyndicateUplinkAccount; var uplinkAcc = uplink.UplinkAccount;
if (uplink != null && _allOriginalNames.ContainsKey(uplink)) if (uplinkAcc != null && _allOriginalNames.ContainsKey(uplinkAcc))
{ {
lines.Add(Loc.GetString("traitor-death-match-end-round-description-entry", lines.Add(Loc.GetString("traitor-death-match-end-round-description-entry",
("originalName", _allOriginalNames[uplink]), ("originalName", _allOriginalNames[uplinkAcc]),
("tcBalance", uplink.Balance))); ("tcBalance", uplinkAcc.Balance)));
} }
} }
return string.Join('\n', lines); return string.Join('\n', lines);

View File

@@ -42,7 +42,7 @@ namespace Content.Server.IoC
IoCManager.Register<RecipeManager, RecipeManager>(); IoCManager.Register<RecipeManager, RecipeManager>();
IoCManager.Register<AlertManager, AlertManager>(); IoCManager.Register<AlertManager, AlertManager>();
IoCManager.Register<ActionManager, ActionManager>(); IoCManager.Register<ActionManager, ActionManager>();
IoCManager.Register<IPDAUplinkManager,PDAUplinkManager>(); IoCManager.Register<IUplinkManager,UplinkManager>();
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>(); IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
IoCManager.Register<BlackboardManager, BlackboardManager>(); IoCManager.Register<BlackboardManager, BlackboardManager>();
IoCManager.Register<ConsiderationsManager, ConsiderationsManager>(); IoCManager.Register<ConsiderationsManager, ConsiderationsManager>();

View File

@@ -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
{
/// <summary>
/// This is simplified version of <see cref="HandheldLightComponent"/>.
/// It doesn't consume any power and can be toggle only by verb.
/// </summary>
[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<UnpoweredFlashlightComponent>
{
protected override void GetData(IEntity user, UnpoweredFlashlightComponent component, VerbData data)
{
var canInteract = EntitySystem.Get<ActionBlockerSystem>().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<UnpoweredFlashlightSystem>().ToggleLight(component);
}
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,443 +1,44 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Access.Components; using Content.Server.Access.Components;
using Content.Server.Hands.Components; using Content.Shared.Containers.ItemSlots;
using Content.Server.Items;
using Content.Server.PDA.Managers;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Tag;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Server.PDA namespace Content.Server.PDA
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IAccess))] [ComponentReference(typeof(IAccess))]
public class PDAComponent : SharedPDAComponent, IInteractUsing, IActivate, IUse, IAccess, IMapInit public class PDAComponent : Component, IAccess
{ {
[Dependency] private readonly IPDAUplinkManager _uplinkManager = default!; public override string Name => "PDA";
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] private ContainerSlot _idSlot = default!; public const string IDSlotName = "pda_id_slot";
[ViewVariables] private ContainerSlot _penSlot = default!; 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] public IdCardComponent? ContainedID;
[ViewVariables] [DataField("pen")] private string? _startingPen = "Pen"; [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; } // TODO: Move me to ECS after Access refactoring
[ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntity == null; #region Acces Logic
[ViewVariables] public bool PenSlotEmpty => _penSlot.ContainedEntity == null; [ViewVariables] private readonly PDAAccessSet _accessSet;
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");
public PDAComponent() public PDAComponent()
{ {
_accessSet = new PdaAccessSet(this); _accessSet = new PDAAccessSet(this);
} }
protected override void Initialize() public ISet<string>? GetContainedAccess()
{
base.Initialize();
_idSlot = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, "pda_entity_container");
_penSlot = ContainerHelpers.EnsureContainer<ContainerSlot>(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<IdCardComponent>();
_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<ItemComponent>());
}
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<ItemComponent>());
}
// Insert Pen
_penSlot.Insert(item);
UpdatePDAUserInterface();
return true;
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
var item = eventArgs.Using;
if (item.TryGetComponent<IdCardComponent>(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);
}
/// <summary>
/// Initialize the PDA's syndicate uplink account.
/// </summary>
/// <param name="acc"></param>
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<HandsComponent>();
var cardItemComponent = cardEntity.GetComponent<ItemComponent>();
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<HandsComponent>();
var itemComponent = pen.GetComponent<ItemComponent>();
hands.PutInHandOrDrop(itemComponent);
UpdatePDAUserInterface();
}
[Verb]
public sealed class EjectIDVerb : Verb<PDAComponent>
{
public override bool AlternativeInteraction => true;
protected override void GetData(IEntity user, PDAComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<PDAComponent>
{
protected override void GetData(IEntity user, PDAComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<PDAComponent>
{
protected override void GetData(IEntity user, PDAComponent component, VerbData data)
{
if (!EntitySystem.Get<ActionBlockerSystem>().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<string>? GetContainedAccess()
{ {
return ContainedID?.Owner?.GetComponent<AccessComponent>()?.Tags; return ContainedID?.Owner?.GetComponent<AccessComponent>()?.Tags;
} }
@@ -450,117 +51,77 @@ namespace Content.Server.PDA
{ {
throw new NotSupportedException("PDA access list is read-only."); throw new NotSupportedException("PDA access list is read-only.");
} }
#endregion
private sealed class PdaAccessSet : ISet<string> // TODO: replace me with dynamic verbs for ItemSlotsSystem
#region Verbs
[Verb]
public sealed class EjectPenVerb : Verb<PDAComponent>
{ {
private readonly PDAComponent _pdaComponent; protected override void GetData(IEntity user, PDAComponent component, VerbData data)
private static readonly HashSet<string> EmptySet = new();
public PdaAccessSet(PDAComponent pdaComponent)
{ {
_pdaComponent = pdaComponent; data.Visibility = VerbVisibility.Invisible;
if (!component.Owner.TryGetComponent(out SharedItemSlotsComponent? slots))
return;
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
return;
var item = EntitySystem.Get<SharedItemSlotsSystem>().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<string> GetEnumerator() protected override void Activate(IEntity user, PDAComponent pda)
{ {
var contained = _pdaComponent.GetContainedAccess() ?? EmptySet; var entityManager = pda.Owner.EntityManager;
return contained.GetEnumerator(); if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
{
entityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>().
TryEjectContent(itemSlots, PenSlotName, user);
}
} }
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
void ICollection<string>.Add(string item)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public void ExceptWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public void IntersectWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public bool IsProperSubsetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsProperSubsetOf(other);
}
public bool IsProperSupersetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsProperSupersetOf(other);
}
public bool IsSubsetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsSubsetOf(other);
}
public bool IsSupersetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsSupersetOf(other);
}
public bool Overlaps(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.Overlaps(other);
}
public bool SetEquals(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.SetEquals(other);
}
public void SymmetricExceptWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public void UnionWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
bool ISet<string>.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<PDAComponent>
{
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<ActionBlockerSystem>().CanInteract(user))
return;
var item = EntitySystem.Get<SharedItemSlotsSystem>().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<SharedItemSlotsSystem>().
TryEjectContent(itemSlots, IDSlotName, user);
}
}
}
#endregion
} }
} }

View File

@@ -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<PDAComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<PDAComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PDAComponent, ActivateInWorldEvent>(OnActivateInWorld);
SubscribeLocalEvent<PDAComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<PDAComponent, ItemSlotChanged>(OnItemSlotChanged);
SubscribeLocalEvent<PDAComponent, LightToggleEvent>(OnLightToggle);
SubscribeLocalEvent<PDAComponent, UplinkInitEvent>(OnUplinkInit);
SubscribeLocalEvent<PDAComponent, UplinkRemovedEvent>(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<UplinkComponent>();
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;
}
}
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Content.Server.PDA
{
public sealed class PDAAccessSet : ISet<string>
{
private readonly PDAComponent _pdaComponent;
private static readonly HashSet<string> EmptySet = new();
public PDAAccessSet(PDAComponent pdaComponent)
{
_pdaComponent = pdaComponent;
}
public IEnumerator<string> GetEnumerator()
{
var contained = _pdaComponent.GetContainedAccess() ?? EmptySet;
return contained.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
void ICollection<string>.Add(string item)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public void ExceptWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public void IntersectWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public bool IsProperSubsetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsProperSubsetOf(other);
}
public bool IsProperSupersetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsProperSupersetOf(other);
}
public bool IsSubsetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsSubsetOf(other);
}
public bool IsSupersetOf(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.IsSupersetOf(other);
}
public bool Overlaps(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.Overlaps(other);
}
public bool SetEquals(IEnumerable<string> other)
{
var set = _pdaComponent.GetContainedAccess() ?? EmptySet;
return set.SetEquals(other);
}
public void SymmetricExceptWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
public void UnionWith(IEnumerable<string> other)
{
throw new NotSupportedException("PDA access list is read-only.");
}
bool ISet<string>.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;
}
}

View File

@@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using Content.Server.Access.Components; using Content.Server.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Inventory.Components; using Content.Server.Inventory.Components;
@@ -133,7 +134,12 @@ namespace Content.Server.Sandbox
{ {
if (pda.ContainedID == null) if (pda.ContainedID == null)
{ {
pda.InsertIdCard(CreateFreshId().GetComponent<IdCardComponent>()); var newID = CreateFreshId();
if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
{
_entityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>().
TryInsertContent(itemSlots, newID, PDAComponent.IDSlotName);
}
} }
else else
{ {

View File

@@ -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 <username> <item-id>";
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<IPlayerManager>().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<IEntityManager>();
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<IConfigurationManager>();
var tcCount = configManager.GetCVar(CCVars.TraitorStartingBalance);
// Get account
var uplinkManager = IoCManager.Resolve<IUplinkManager>();
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<UplinkSystem>()
.AddUplink(user, uplinkAccount!, uplinkEntity))
{
shell.WriteLine(Loc.GetString("Failed to add uplink to the player"));
return;
}
}
}
}

View File

@@ -1,12 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Traitor.Uplink;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Map; using Robust.Shared.Map;
namespace Content.Server.PDA.Managers namespace Content.Server.PDA.Managers
{ {
public interface IPDAUplinkManager public interface IUplinkManager
{ {
public IReadOnlyDictionary<string, UplinkListingData> FetchListings { get; } public IReadOnlyDictionary<string, UplinkListingData> FetchListings { get; }
@@ -16,6 +17,8 @@ namespace Content.Server.PDA.Managers
public bool ChangeBalance(UplinkAccount acc, int amt); public bool ChangeBalance(UplinkAccount acc, int amt);
public bool TryGetAccount(EntityUid owner, out UplinkAccount? acc);
public bool TryPurchaseItem( public bool TryPurchaseItem(
UplinkAccount? acc, UplinkAccount? acc,
string itemId, string itemId,

View File

@@ -2,19 +2,21 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Server.Mind.Components; using Content.Server.Mind.Components;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Traitor.Uplink;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using System.Linq;
namespace Content.Server.PDA.Managers namespace Content.Server.PDA.Managers
{ {
public class PDAUplinkManager : IPDAUplinkManager public class UplinkManager : IUplinkManager
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
private readonly List<UplinkAccount> _accounts = new(); private readonly Dictionary<EntityUid, UplinkAccount> _accounts = new();
private readonly Dictionary<string, UplinkListingData> _listings = new(); private readonly Dictionary<string, UplinkListingData> _listings = new();
public IReadOnlyDictionary<string, UplinkListingData> FetchListings => _listings; public IReadOnlyDictionary<string, UplinkListingData> FetchListings => _listings;
@@ -52,18 +54,23 @@ namespace Content.Server.PDA.Managers
return false; return false;
} }
if (_accounts.Contains(acc)) if (_accounts.ContainsKey(acc.AccountHolder))
{ {
return false; return false;
} }
_accounts.Add(acc); _accounts.Add(acc.AccountHolder, acc);
return true; return true;
} }
public bool TryGetAccount(EntityUid owner, out UplinkAccount? acc)
{
return _accounts.TryGetValue(owner, out acc);
}
public bool ChangeBalance(UplinkAccount acc, int amt) 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) if (account == null)
{ {

View File

@@ -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;
}
}

View File

@@ -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
{
}
}

View File

@@ -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<UplinkComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<UplinkComponent, ComponentRemove>(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<UplinkComponent>();
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<PDAComponent>().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<PDAComponent>())
return item.Owner;
}
}
return null;
}
}
}

View File

@@ -2,6 +2,7 @@ using System.Threading.Tasks;
using Content.Server.Inventory.Components; using Content.Server.Inventory.Components;
using Content.Server.Mind.Components; using Content.Server.Mind.Components;
using Content.Server.PDA; using Content.Server.PDA;
using Content.Server.Traitor.Uplink.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Popups; using Content.Shared.Popups;
@@ -40,7 +41,7 @@ namespace Content.Server.TraitorDeathMatch.Components
return false; return false;
} }
if (!eventArgs.Using.TryGetComponent<PDAComponent>(out var victimPDA)) if (!eventArgs.Using.TryGetComponent<UplinkComponent>(out var victimUplink))
{ {
Owner.PopupMessage(eventArgs.User, Loc.GetString("traitor-death-match-redemption-component-interact-using-main-message", 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")))); ("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; var userPDAEntity = userInv.GetSlotItem(EquipmentSlotDefines.Slots.IDCARD)?.Owner;
PDAComponent? userPDA = null; UplinkComponent? userUplink = null;
if (userPDAEntity != null) if (userPDAEntity != null)
if (userPDAEntity.TryGetComponent<PDAComponent>(out var userPDAComponent)) if (userPDAEntity.TryGetComponent<UplinkComponent>(out var userUplinkComponent))
userPDA = userPDAComponent; userUplink = userUplinkComponent;
if (userPDA == null) if (userUplink == null)
{ {
Owner.PopupMessage(eventArgs.User, Loc.GetString("traitor-death-match-redemption-component-interact-using-main-message", 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")))); ("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. // We have finally determined both PDA components. FINALLY.
var userAccount = userPDA.SyndicateUplinkAccount; var userAccount = userUplink.UplinkAccount;
var victimAccount = victimPDA.SyndicateUplinkAccount; var victimAccount = victimUplink.UplinkAccount;
if (userAccount == null) if (userAccount == null)
{ {
@@ -107,7 +108,7 @@ namespace Content.Server.TraitorDeathMatch.Components
victimAccount.ModifyAccountBalance(0); victimAccount.ModifyAccountBalance(0);
userAccount.ModifyAccountBalance(userAccount.Balance + transferAmount); 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))); Owner.PopupMessage(eventArgs.User, Loc.GetString("traitor-death-match-redemption-component-interact-using-success-message", ("tcAmount", transferAmount)));
return true; return true;

View File

@@ -0,0 +1,23 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.Containers.ItemSlots
{
/// <summary>
/// Item was placed in or removed from one of the slots in <see cref="SharedItemSlotsComponent"/>
/// </summary>
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;
}
}
}

View File

@@ -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
{
/// <summary>
/// Used for entities that can hold items in different slots
/// Allows basic insert/eject interaction
/// </summary>
[RegisterComponent]
public class SharedItemSlotsComponent : Component
{
public override string Name => "ItemSlots";
[ViewVariables] [DataField("slots")] public Dictionary<string, ItemSlot> 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<EntityPrototype>))]
[ViewVariables] public string? StartingItem;
[ViewVariables] public ContainerSlot ContainerSlot = default!;
}
}

View File

@@ -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<SharedItemSlotsComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<SharedItemSlotsComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SharedItemSlotsComponent, InteractUsingEvent>(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<ContainerSlot>(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);
}
/// <summary>
/// Tries to insert item in any fitting item slot from users hand
/// </summary>
/// <returns>False if failed to insert item</returns>
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;
}
/// <summary>
/// Tries to insert item in known slot. Doesn't interact with user
/// </summary>
/// <returns>False if failed to insert item</returns>
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;
}
/// <summary>
/// Check if slot has some content in it (without ejecting item)
/// </summary>
/// <returns>Null if doesn't have any content</returns>
public IEntity? PeekItemInSlot(SharedItemSlotsComponent itemSlots, string slotName)
{
if (!itemSlots.Slots.TryGetValue(slotName, out var slot))
return null;
var item = slot.ContainerSlot.ContainedEntity;
return item;
}
/// <summary>
/// Try to eject item from slot to users hands
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,11 @@
using Robust.Shared.Serialization;
using System;
namespace Content.Shared.Light
{
[Serializable, NetSerializable]
public enum UnpoweredFlashlightVisuals : byte
{
LightOn
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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<UplinkAccount>? 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<UplinkListingData>
{
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;
}
}
}

View File

@@ -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<UplinkAccount>? 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<UplinkListingData>
{
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;
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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
{
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
using Robust.Shared.Serialization;
using System;
namespace Content.Shared.Traitor.Uplink
{
[Serializable, NetSerializable]
public enum UplinkUiKey : byte
{
Key
}
}

View File

@@ -0,0 +1,4 @@
item-slots-try-insert-no-hands = You have no hands.
# EjectItemVerb
eject-item-verb-text-default = Eject {$item}

View File

@@ -0,0 +1,2 @@
# ToggleFlashlightVerb
toggle-flashlight-verb-get-data-text = Toggle flashlight

View File

@@ -1,9 +1,6 @@
### UI ### 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 # For the PDA screen
comp-pda-ui = ID: [color=white]{$Owner}[/color], [color=yellow]{$JobTitle}[/color] 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] 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-main-menu-tab-title = Main Menu
pda-bound-user-interface-uplink-tab-title = Uplink 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-eject-pen-button = Eject Pen
comp-pda-ui-toggle-flashlight-button = Toggle Flashlight 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

View File

@@ -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]

View File

@@ -21,6 +21,7 @@
Slots: Slots:
- idcard - idcard
- Belt - Belt
- type: UnpoweredFlashlight
- type: PointLight - type: PointLight
enabled: false enabled: false
radius: 1.4 radius: 1.4
@@ -30,6 +31,24 @@
interfaces: interfaces:
- key: enum.PDAUiKey.Key - key: enum.PDAUiKey.Key
type: PDABoundUserInterface 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: DoorBumpOpener
- type: entity - type: entity