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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ namespace Content.Server.IoC
IoCManager.Register<RecipeManager, RecipeManager>();
IoCManager.Register<AlertManager, AlertManager>();
IoCManager.Register<ActionManager, ActionManager>();
IoCManager.Register<IPDAUplinkManager,PDAUplinkManager>();
IoCManager.Register<IUplinkManager,UplinkManager>();
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
IoCManager.Register<BlackboardManager, BlackboardManager>();
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.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Access.Components;
using Content.Server.Hands.Components;
using Content.Server.Items;
using Content.Server.PDA.Managers;
using Content.Server.UserInterface;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Content.Shared.PDA;
using Content.Shared.Popups;
using Content.Shared.Sound;
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.PDA
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IAccess))]
public class PDAComponent : SharedPDAComponent, IInteractUsing, IActivate, IUse, IAccess, IMapInit
public class PDAComponent : Component, IAccess
{
[Dependency] private readonly IPDAUplinkManager _uplinkManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public override string Name => "PDA";
[ViewVariables] private ContainerSlot _idSlot = default!;
[ViewVariables] private ContainerSlot _penSlot = default!;
public const string IDSlotName = "pda_id_slot";
public const string PenSlotName = "pda_pen_slot";
[ViewVariables] private bool _lightOn;
[ViewVariables] [DataField("idCard")] public string? StartingIdCard;
[ViewVariables] [DataField("idCard")] private string? _startingIdCard = "AssistantIDCard";
[ViewVariables] [DataField("pen")] private string? _startingPen = "Pen";
[ViewVariables] public IdCardComponent? ContainedID;
[ViewVariables] public bool PenInserted;
[ViewVariables] public bool FlashlightOn;
[ViewVariables] public string? OwnerName { get; private set; }
[ViewVariables] public string? OwnerName;
[ViewVariables] public IdCardComponent? ContainedID { get; private set; }
[ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntity == null;
[ViewVariables] public bool PenSlotEmpty => _penSlot.ContainedEntity == null;
private UplinkAccount? _syndicateUplinkAccount;
[ViewVariables] public UplinkAccount? SyndicateUplinkAccount => _syndicateUplinkAccount;
[ViewVariables] private readonly PdaAccessSet _accessSet;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PDAUiKey.Key);
[DataField("insertIdSound")] private SoundSpecifier _insertIdSound = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/batrifle_magin.ogg");
[DataField("toggleFlashlightSound")] private SoundSpecifier _toggleFlashlightSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg");
[DataField("ejectIdSound")] private SoundSpecifier _ejectIdSound = new SoundPathSpecifier("/Audio/Machines/id_swipe.ogg");
// TODO: Move me to ECS after Access refactoring
#region Acces Logic
[ViewVariables] private readonly PDAAccessSet _accessSet;
public PDAComponent()
{
_accessSet = new PdaAccessSet(this);
_accessSet = new PDAAccessSet(this);
}
protected override void Initialize()
{
base.Initialize();
_idSlot = ContainerHelpers.EnsureContainer<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()
public ISet<string>? GetContainedAccess()
{
return ContainedID?.Owner?.GetComponent<AccessComponent>()?.Tags;
}
@@ -450,117 +51,77 @@ namespace Content.Server.PDA
{
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;
private static readonly HashSet<string> EmptySet = new();
public PdaAccessSet(PDAComponent pdaComponent)
protected override void GetData(IEntity user, PDAComponent component, VerbData data)
{
_pdaComponent = pdaComponent;
data.Visibility = VerbVisibility.Invisible;
if (!component.Owner.TryGetComponent(out SharedItemSlotsComponent? slots))
return;
if (!EntitySystem.Get<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;
return contained.GetEnumerator();
var entityManager = pda.Owner.EntityManager;
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 Content.Server.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Server.GameTicking;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
@@ -133,7 +134,12 @@ namespace Content.Server.Sandbox
{
if (pda.ContainedID == null)
{
pda.InsertIdCard(CreateFreshId().GetComponent<IdCardComponent>());
var newID = CreateFreshId();
if (pda.Owner.TryGetComponent(out SharedItemSlotsComponent? itemSlots))
{
_entityManager.EntitySysManager.GetEntitySystem<SharedItemSlotsSystem>().
TryInsertContent(itemSlots, newID, PDAComponent.IDSlotName);
}
}
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.Diagnostics.CodeAnalysis;
using Content.Shared.PDA;
using Content.Shared.Traitor.Uplink;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Server.PDA.Managers
{
public interface IPDAUplinkManager
public interface IUplinkManager
{
public IReadOnlyDictionary<string, UplinkListingData> FetchListings { get; }
@@ -16,6 +17,8 @@ namespace Content.Server.PDA.Managers
public bool ChangeBalance(UplinkAccount acc, int amt);
public bool TryGetAccount(EntityUid owner, out UplinkAccount? acc);
public bool TryPurchaseItem(
UplinkAccount? acc,
string itemId,

View File

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

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