Merge branch 'master' into air-alarm-fixup
This commit is contained in:
@@ -41,7 +41,7 @@ namespace Content.Server.AI.Operators.Inventory
|
||||
|
||||
if (!storageComponent.Open)
|
||||
{
|
||||
IoCManager.Resolve<EntityStorageSystem>().ToggleOpen(_owner, _target, storageComponent);
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<EntityStorageSystem>().ToggleOpen(_owner, _target, storageComponent);
|
||||
}
|
||||
|
||||
var blackboard = UtilityAiHelpers.GetBlackboard(_owner);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Clothing
|
||||
{
|
||||
return result;
|
||||
}
|
||||
var containerSystem = IoCManager.Resolve<ContainerSystem>();
|
||||
var containerSystem = entMan.EntitySysManager.GetEntitySystem<ContainerSystem>();
|
||||
foreach (var entity in Visibility.GetNearestEntities(entMan.GetComponent<TransformComponent>(Owner).Coordinates, typeof(ClothingComponent), controller.VisionRadius))
|
||||
{
|
||||
if (containerSystem.TryGetContainingContainer(entity, out var container))
|
||||
|
||||
@@ -8,7 +8,9 @@ using Content.Shared.Access.Systems;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Access.Components
|
||||
{
|
||||
@@ -18,9 +20,13 @@ namespace Content.Server.Access.Components
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(IdCardConsoleUiKey.Key);
|
||||
|
||||
private StationRecordsSystem? _recordSystem;
|
||||
private StationSystem? _stationSystem;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -28,6 +34,9 @@ namespace Content.Server.Access.Components
|
||||
Owner.EnsureComponentWarn<AccessReaderComponent>();
|
||||
Owner.EnsureComponentWarn<ServerUserInterfaceComponent>();
|
||||
|
||||
_stationSystem = _entities.EntitySysManager.GetEntitySystem<StationSystem>();
|
||||
_recordSystem = _entities.EntitySysManager.GetEntitySystem<StationRecordsSystem>();
|
||||
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
@@ -45,7 +54,7 @@ namespace Content.Server.Access.Components
|
||||
switch (obj.Message)
|
||||
{
|
||||
case WriteToTargetIdMessage msg:
|
||||
TryWriteToTargetId(msg.FullName, msg.JobTitle, msg.AccessList, player);
|
||||
TryWriteToTargetId(msg.FullName, msg.JobTitle, msg.AccessList, msg.JobPrototype, player);
|
||||
UpdateUserInterface();
|
||||
break;
|
||||
}
|
||||
@@ -62,7 +71,7 @@ namespace Content.Server.Access.Components
|
||||
}
|
||||
|
||||
var privilegedIdEntity = PrivilegedIdSlot.Item;
|
||||
var accessSystem = EntitySystem.Get<AccessReaderSystem>();
|
||||
var accessSystem = _entities.EntitySysManager.GetEntitySystem<AccessReaderSystem>();
|
||||
return privilegedIdEntity != null && accessSystem.IsAllowed(privilegedIdEntity.Value, reader);
|
||||
}
|
||||
|
||||
@@ -70,12 +79,12 @@ namespace Content.Server.Access.Components
|
||||
/// Called whenever an access button is pressed, adding or removing that access from the target ID card.
|
||||
/// Writes data passed from the UI into the ID stored in <see cref="TargetIdSlot"/>, if present.
|
||||
/// </summary>
|
||||
private void TryWriteToTargetId(string newFullName, string newJobTitle, List<string> newAccessList, EntityUid player)
|
||||
private void TryWriteToTargetId(string newFullName, string newJobTitle, List<string> newAccessList, string newJobProto, EntityUid player)
|
||||
{
|
||||
if (TargetIdSlot.Item is not {Valid: true} targetIdEntity || !PrivilegedIdIsAuthorized())
|
||||
return;
|
||||
|
||||
var cardSystem = EntitySystem.Get<IdCardSystem>();
|
||||
var cardSystem = _entities.EntitySysManager.GetEntitySystem<IdCardSystem>();
|
||||
cardSystem.TryChangeFullName(targetIdEntity, newFullName, player: player);
|
||||
cardSystem.TryChangeJobTitle(targetIdEntity, newJobTitle, player: player);
|
||||
|
||||
@@ -85,7 +94,7 @@ namespace Content.Server.Access.Components
|
||||
return;
|
||||
}
|
||||
|
||||
var accessSystem = EntitySystem.Get<AccessSystem>();
|
||||
var accessSystem = _entities.EntitySysManager.GetEntitySystem<AccessSystem>();
|
||||
accessSystem.TrySetTags(targetIdEntity, newAccessList);
|
||||
|
||||
/*TODO: ECS IdCardConsoleComponent and then log on card ejection, together with the save.
|
||||
@@ -93,17 +102,17 @@ namespace Content.Server.Access.Components
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{_entities.ToPrettyString(player):player} has modified {_entities.ToPrettyString(targetIdEntity):entity} with the following accesses: [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
UpdateStationRecord(targetIdEntity, newFullName, newJobTitle);
|
||||
UpdateStationRecord(targetIdEntity, newFullName, newJobTitle, newJobProto);
|
||||
}
|
||||
|
||||
private void UpdateStationRecord(EntityUid idCard, string newFullName, string newJobTitle)
|
||||
private void UpdateStationRecord(EntityUid idCard, string newFullName, string newJobTitle, string newJobProto)
|
||||
{
|
||||
var station = EntitySystem.Get<StationSystem>().GetOwningStation(Owner);
|
||||
var recordSystem = EntitySystem.Get<StationRecordsSystem>();
|
||||
var station = _stationSystem?.GetOwningStation(Owner);
|
||||
if (station == null
|
||||
|| _recordSystem == null
|
||||
|| !_entities.TryGetComponent(idCard, out StationRecordKeyStorageComponent? keyStorage)
|
||||
|| keyStorage.Key == null
|
||||
|| !recordSystem.TryGetRecord(station.Value, keyStorage.Key.Value, out GeneralStationRecord? record))
|
||||
|| !_recordSystem.TryGetRecord(station.Value, keyStorage.Key.Value, out GeneralStationRecord? record))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -111,7 +120,13 @@ namespace Content.Server.Access.Components
|
||||
record.Name = newFullName;
|
||||
record.JobTitle = newJobTitle;
|
||||
|
||||
recordSystem.Synchronize(station.Value);
|
||||
if (_prototypeManager.TryIndex(newJobProto, out JobPrototype? job))
|
||||
{
|
||||
record.JobPrototype = newJobProto;
|
||||
record.JobIcon = job.Icon;
|
||||
}
|
||||
|
||||
_recordSystem.Synchronize(station.Value);
|
||||
}
|
||||
|
||||
public void UpdateUserInterface()
|
||||
@@ -137,6 +152,7 @@ namespace Content.Server.Access.Components
|
||||
null,
|
||||
null,
|
||||
privilegedIdName,
|
||||
string.Empty,
|
||||
string.Empty);
|
||||
}
|
||||
else
|
||||
@@ -146,6 +162,19 @@ namespace Content.Server.Access.Components
|
||||
var name = string.Empty;
|
||||
if (PrivilegedIdSlot.Item is {Valid: true} item)
|
||||
name = _entities.GetComponent<MetaDataComponent>(item).EntityName;
|
||||
|
||||
var station = _stationSystem?.GetOwningStation(Owner);
|
||||
var jobProto = string.Empty;
|
||||
if (_recordSystem != null
|
||||
&& station != null
|
||||
&& _entities.TryGetComponent(targetIdEntity, out StationRecordKeyStorageComponent? keyStorage)
|
||||
&& keyStorage.Key != null
|
||||
&& _recordSystem.TryGetRecord(station.Value, keyStorage.Key.Value,
|
||||
out GeneralStationRecord? record))
|
||||
{
|
||||
jobProto = record.JobPrototype;
|
||||
}
|
||||
|
||||
newState = new IdCardConsoleBoundUserInterfaceState(
|
||||
PrivilegedIdSlot.HasItem,
|
||||
PrivilegedIdIsAuthorized(),
|
||||
@@ -153,6 +182,7 @@ namespace Content.Server.Access.Components
|
||||
targetIdComponent.FullName,
|
||||
targetIdComponent.JobTitle,
|
||||
targetAccessComponent.Tags.ToArray(),
|
||||
jobProto,
|
||||
name,
|
||||
_entities.GetComponent<MetaDataComponent>(targetIdEntity).EntityName);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Server.Access.Systems
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (!TryComp<AccessComponent>(args.Target, out var targetAccess) || !TryComp<IdCardComponent>(uid, out var targetIDCard) || args.Target == null)
|
||||
if (!TryComp<AccessComponent>(args.Target, out var targetAccess) || !TryComp<IdCardComponent>(args.Target, out var targetIDCard) || args.Target == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<AccessComponent>(uid, out var access) || !TryComp<IdCardComponent>(uid, out var idCard))
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Popups;
|
||||
@@ -9,11 +5,11 @@ using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Access.Systems
|
||||
{
|
||||
@@ -27,13 +23,15 @@ namespace Content.Server.Access.Systems
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IdCardComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<IdCardComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<IdCardComponent, BeingMicrowavedEvent>(OnMicrowaved);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, IdCardComponent id, ComponentInit args)
|
||||
private void OnMapInit(EntityUid uid, IdCardComponent id, MapInitEvent args)
|
||||
{
|
||||
id.OriginalOwnerName ??= EntityManager.GetComponent<MetaDataComponent>(id.Owner).EntityName;
|
||||
// On one hand, these prototypes should default to having the correct name. On the other hand, id cards are
|
||||
// rarely ever spawned in on their own without an owner, so this is fine.
|
||||
id.OriginalEntityName ??= EntityManager.GetComponent<MetaDataComponent>(id.Owner).EntityName;
|
||||
UpdateEntityName(uid, id);
|
||||
}
|
||||
|
||||
@@ -139,7 +137,7 @@ namespace Content.Server.Access.Systems
|
||||
|
||||
if (string.IsNullOrWhiteSpace(id.FullName) && string.IsNullOrWhiteSpace(id.JobTitle))
|
||||
{
|
||||
EntityManager.GetComponent<MetaDataComponent>(id.Owner).EntityName = id.OriginalOwnerName;
|
||||
EntityManager.GetComponent<MetaDataComponent>(id.Owner).EntityName = id.OriginalEntityName;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ namespace Content.Server.Access.Systems
|
||||
|
||||
var val = string.IsNullOrWhiteSpace(id.FullName)
|
||||
? Loc.GetString("access-id-card-component-owner-name-job-title-text",
|
||||
("originalOwnerName", id.OriginalOwnerName),
|
||||
("originalOwnerName", id.OriginalEntityName),
|
||||
("jobSuffix", jobSuffix))
|
||||
: Loc.GetString("access-id-card-component-owner-full-name-job-title-text",
|
||||
("fullName", id.FullName),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Access.Components;
|
||||
using Content.Server.Access.Components;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Inventory;
|
||||
@@ -66,7 +66,7 @@ public sealed class IdExaminableSystem : EntitySystem
|
||||
|
||||
var val = string.IsNullOrWhiteSpace(id.FullName)
|
||||
? Loc.GetString("access-id-card-component-owner-name-job-title-text",
|
||||
("originalOwnerName", id.OriginalOwnerName),
|
||||
("originalOwnerName", id.OriginalEntityName),
|
||||
("jobSuffix", jobSuffix))
|
||||
: Loc.GetString("access-id-card-component-owner-full-name-job-title-text",
|
||||
("fullName", id.FullName),
|
||||
|
||||
@@ -37,6 +37,7 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Tabletop.Components;
|
||||
@@ -70,6 +71,7 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly TabletopSystem _tabletopSystem = default!;
|
||||
[Dependency] private readonly VomitSystem _vomitSystem = default!;
|
||||
[Dependency] private readonly WeldableSystem _weldableSystem = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!;
|
||||
|
||||
// All smite verbs have names so invokeverb works.
|
||||
private void AddSmiteVerbs(GetVerbsEvent<Verb> args)
|
||||
@@ -810,5 +812,23 @@ public sealed partial class AdminVerbSystem
|
||||
Message = Loc.GetString("admin-smite-disarm-prone-description"),
|
||||
};
|
||||
args.Verbs.Add(disarmProne);
|
||||
|
||||
Verb superSpeed = new()
|
||||
{
|
||||
Text = "Super speed",
|
||||
Category = VerbCategory.Smite,
|
||||
IconTexture = "/Textures/Interface/AdminActions/super_speed.png",
|
||||
Act = () =>
|
||||
{
|
||||
var movementSpeed = EnsureComp<MovementSpeedModifierComponent>(args.Target);
|
||||
_movementSpeedModifierSystem?.ChangeBaseSpeed(args.Target, 400, 8000, 40, movementSpeed);
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("admin-smite-super-speed-prompt"), args.Target,
|
||||
Filter.Entities(args.Target), PopupType.LargeCaution);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-smite-super-speed-description"),
|
||||
};
|
||||
args.Verbs.Add(superSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public sealed class AlertLevelSystem : EntitySystem
|
||||
{
|
||||
if (detail.Sound != null)
|
||||
{
|
||||
var filter = _stationSystem.GetInStation(station);
|
||||
var filter = _stationSystem.GetInOwningStation(station);
|
||||
SoundSystem.Play(detail.Sound.GetSound(), filter, detail.Sound.Params);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Server.Arcade.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Arcade;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
using Robust.Server.Player;
|
||||
|
||||
namespace Content.Server.Arcade
|
||||
{
|
||||
@@ -19,6 +19,7 @@ namespace Content.Server.Arcade
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BlockGameArcadeComponent, AfterActivatableUIOpenEvent>(OnAfterUIOpen);
|
||||
SubscribeLocalEvent<SpaceVillainArcadeComponent, AfterActivatableUIOpenEvent>(OnAfterUIOpenSV);
|
||||
SubscribeLocalEvent<BlockGameArcadeComponent, BoundUIClosedEvent>((_,c,args) => c.UnRegisterPlayerSession((IPlayerSession)args.Session));
|
||||
InitializeBlockGame();
|
||||
InitializeSpaceVillain();
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Content.Server.Arcade.Components
|
||||
UpdatePlayerStatus(temp);
|
||||
}
|
||||
|
||||
private void UnRegisterPlayerSession(IPlayerSession session)
|
||||
public void UnRegisterPlayerSession(IPlayerSession session)
|
||||
{
|
||||
if (_player == session)
|
||||
{
|
||||
@@ -72,7 +72,6 @@ namespace Content.Server.Arcade.Components
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
UserInterface.OnClosed += UnRegisterPlayerSession;
|
||||
}
|
||||
_game = new BlockGame(this);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
// TODO: ECS.
|
||||
|
||||
namespace Content.Server.Arcade.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
@@ -54,7 +56,11 @@ namespace Content.Server.Arcade.Components
|
||||
{
|
||||
"ToyMouse", "ToyAi", "ToyNuke", "ToyAssistant", "ToyGriffin", "ToyHonk", "ToyIan",
|
||||
"ToyMarauder", "ToyMauler", "ToyGygax", "ToyOdysseus", "ToyOwlman", "ToyDeathRipley",
|
||||
"ToyPhazon", "ToyFireRipley", "ToyReticence", "ToyRipley", "ToySeraph", "ToyDurand", "ToySkeleton"
|
||||
"ToyPhazon", "ToyFireRipley", "ToyReticence", "ToyRipley", "ToySeraph", "ToyDurand", "ToySkeleton",
|
||||
"FoamCrossbow", "RevolverCapGun", "PlushieLizard", "PlushieSpaceLizard",
|
||||
"PlushieNuke", "PlushieCarp", "PlushieRatvar", "PlushieNar", "PlushieSnake", "Basketball", "Football",
|
||||
"PlushieRouny", "PlushieBee", "PlushieSlime", "BalloonCorgi", "ToySword", "CrayonBox", "BoxDonkSoftBox", "BoxCartridgeCap",
|
||||
"HarmonicaInstrument", "OcarinaInstrument", "RecorderInstrument", "GunpetInstrument", "BirdToyInstrument"
|
||||
};
|
||||
|
||||
protected override void Initialize()
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace Content.Server.Atmos.Components
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
|
||||
UserInterface.OnClosed += UserInterfaceOnClose;
|
||||
}
|
||||
|
||||
_entities.TryGetComponent(Owner, out _appearance);
|
||||
@@ -96,12 +95,7 @@ namespace Content.Server.Atmos.Components
|
||||
Resync();
|
||||
}
|
||||
|
||||
private void UserInterfaceOnClose(IPlayerSession obj)
|
||||
{
|
||||
UpdateAppearance(false);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(bool open)
|
||||
public void UpdateAppearance(bool open)
|
||||
{
|
||||
_appearance?.SetData(GasAnalyzerVisuals.VisualState,
|
||||
open ? GasAnalyzerVisualState.Working : GasAnalyzerVisualState.Off);
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasAnalyzableComponent, GetVerbsEvent<ExamineVerb>>(OnGetExamineVerbs);
|
||||
SubscribeLocalEvent<GasAnalyzerComponent, BoundUIClosedEvent>((_,c,_) => c.UpdateAppearance(false));
|
||||
}
|
||||
|
||||
private void OnGetExamineVerbs(EntityUid uid, GasAnalyzableComponent component, GetVerbsEvent<ExamineVerb> args)
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
|
||||
private Filter GetStationAndPvs(EntityUid source)
|
||||
{
|
||||
var stationFilter = _stationSystem.GetInStation(source);
|
||||
var stationFilter = _stationSystem.GetInOwningStation(source);
|
||||
stationFilter.AddPlayersByPvs(source, entityManager: EntityManager);
|
||||
return stationFilter;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Content.Server.Bible
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Target == null || args.Target == args.User || _mobStateSystem.IsDead(args.Target.Value))
|
||||
if (args.Target == null || args.Target == args.User || !_mobStateSystem.IsAlive(args.Target.Value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ namespace Content.Server.Body.Components
|
||||
mechanism.Owner.RandomOffset(0.25f);
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
public void MapInitialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_mechanismContainer = Owner.EnsureContainer<Container>($"{Name}-{nameof(BodyPartComponent)}");
|
||||
_mechanismContainer = Owner.EnsureContainer<Container>(ContainerId);
|
||||
|
||||
// This is ran in Startup as entities spawned in Initialize
|
||||
// are not synced to the client since they are assumed to be
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Content.Server.Body.Systems
|
||||
SubscribeLocalEvent<BodyComponent, MoveInputEvent>(OnRelayMoveInput);
|
||||
SubscribeLocalEvent<BodyComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
|
||||
SubscribeLocalEvent<BodyComponent, BeingMicrowavedEvent>(OnBeingMicrowaved);
|
||||
SubscribeLocalEvent<BodyPartComponent, MapInitEvent>((_, c, _) => c.MapInitialize());
|
||||
}
|
||||
|
||||
private void OnRelayMoveInput(EntityUid uid, BodyComponent component, ref MoveInputEvent args)
|
||||
|
||||
@@ -85,6 +85,9 @@ namespace Content.Server.Buckle.Systems
|
||||
|
||||
private void ContainerModifiedStrap(EntityUid uid, StrapComponent strap, ContainerModifiedMessage message)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
foreach (var buckledEntity in strap.BuckledEntities)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(buckledEntity, out BuckleComponent? buckled))
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Content.Server.CombatMode
|
||||
SoundSystem.Play(component.DisarmSuccessSound.GetSound(), filterAll, args.Performer, AudioHelpers.WithVariation(0.025f));
|
||||
_adminLogger.Add(LogType.DisarmedAction, $"{ToPrettyString(args.Performer):user} used disarm on {ToPrettyString(args.Target):target}");
|
||||
|
||||
var eventArgs = new DisarmedEvent() { Target = args.Target, Source = args.Performer, PushProbability = HasComp<DisarmProneComponent>(args.Target) ? 1.0f : chance };
|
||||
var eventArgs = new DisarmedEvent() { Target = args.Target, Source = args.Performer, PushProbability = (1 - chance) };
|
||||
RaiseLocalEvent(args.Target, eventArgs, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Disposal.Unit.Components;
|
||||
using Content.Server.Disposal.Unit.Components;
|
||||
|
||||
namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
@@ -7,6 +7,8 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
[ComponentReference(typeof(DisposalTubeComponent))]
|
||||
public sealed class DisposalBendComponent : DisposalTubeComponent
|
||||
{
|
||||
public override string ContainerId => "DisposalBend";
|
||||
|
||||
[DataField("sideDegrees")]
|
||||
private int _sideDegrees = -90;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private const string HolderPrototypeId = "DisposalHolder";
|
||||
public override string ContainerId => "DisposalEntry";
|
||||
|
||||
public bool TryInsert(DisposalUnitComponent from, IEnumerable<string>? tags = default)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Server.Disposal.Unit.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
[ComponentReference(typeof(DisposalTubeComponent))]
|
||||
public class DisposalJunctionComponent : DisposalTubeComponent
|
||||
{
|
||||
public override string ContainerId => "DisposalJunction";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
@@ -37,7 +39,7 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
return _random.Pick(directions);
|
||||
}
|
||||
|
||||
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,19 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
[ComponentReference(typeof(DisposalTubeComponent))]
|
||||
public sealed class DisposalRouterComponent : DisposalJunctionComponent
|
||||
{
|
||||
public override string ContainerId => "DisposalRouter";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<string> _tags = new();
|
||||
public readonly HashSet<string> Tags = new();
|
||||
|
||||
[ViewVariables]
|
||||
public bool Anchored =>
|
||||
!_entMan.TryGetComponent(Owner, out IPhysBody? physics) ||
|
||||
physics.BodyType == BodyType.Static;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalRouterUiKey.Key);
|
||||
[ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalRouterUiKey.Key);
|
||||
|
||||
[DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
|
||||
|
||||
@@ -32,7 +34,7 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
var directions = ConnectableDirections();
|
||||
|
||||
if (holder.Tags.Overlaps(_tags))
|
||||
if (holder.Tags.Overlaps(Tags))
|
||||
{
|
||||
return directions[1];
|
||||
}
|
||||
@@ -48,8 +50,6 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
}
|
||||
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,45 +72,15 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
//Check for correct message and ignore maleformed strings
|
||||
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tags))
|
||||
{
|
||||
_tags.Clear();
|
||||
Tags.Clear();
|
||||
foreach (var tag in msg.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
_tags.Add(tag.Trim());
|
||||
Tags.Add(tag.Trim());
|
||||
ClickSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets component data to be used to update the user interface client-side.
|
||||
/// </summary>
|
||||
/// <returns>Returns a <see cref="DisposalRouterUserInterfaceState"/></returns>
|
||||
private DisposalRouterUserInterfaceState GetUserInterfaceState()
|
||||
{
|
||||
if (_tags.Count <= 0)
|
||||
{
|
||||
return new DisposalRouterUserInterfaceState("");
|
||||
}
|
||||
|
||||
var taglist = new StringBuilder();
|
||||
|
||||
foreach (var tag in _tags)
|
||||
{
|
||||
taglist.Append(tag);
|
||||
taglist.Append(", ");
|
||||
}
|
||||
|
||||
taglist.Remove(taglist.Length - 2, 2);
|
||||
|
||||
return new DisposalRouterUserInterfaceState(taglist.ToString());
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
var state = GetUserInterfaceState();
|
||||
UserInterface?.SetState(state);
|
||||
}
|
||||
|
||||
private void ClickSound()
|
||||
{
|
||||
SoundSystem.Play(_clickSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default.WithVolume(-2f));
|
||||
@@ -121,11 +91,5 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
UserInterface?.CloseAll();
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public void OpenUserInterface(ActorComponent actor)
|
||||
{
|
||||
UpdateUserInterface();
|
||||
UserInterface?.Open(actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,23 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public override string ContainerId => "DisposalTagger";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private string _tag = "";
|
||||
public string Tag = "";
|
||||
|
||||
[ViewVariables]
|
||||
public bool Anchored =>
|
||||
!_entMan.TryGetComponent(Owner, out PhysicsComponent? physics) ||
|
||||
physics.BodyType == BodyType.Static;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalTaggerUiKey.Key);
|
||||
[ViewVariables] public BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalTaggerUiKey.Key);
|
||||
|
||||
[DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
|
||||
|
||||
public override Direction NextDirection(DisposalHolderComponent holder)
|
||||
{
|
||||
holder.Tags.Add(_tag);
|
||||
holder.Tags.Add(Tag);
|
||||
return base.NextDirection(holder);
|
||||
}
|
||||
|
||||
@@ -41,8 +43,6 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
}
|
||||
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,26 +60,11 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
//Check for correct message and ignore maleformed strings
|
||||
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tag))
|
||||
{
|
||||
_tag = msg.Tag;
|
||||
Tag = msg.Tag;
|
||||
ClickSound();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets component data to be used to update the user interface client-side.
|
||||
/// </summary>
|
||||
/// <returns>Returns a <see cref="DisposalTaggerUserInterfaceState"/></returns>
|
||||
private DisposalTaggerUserInterfaceState GetUserInterfaceState()
|
||||
{
|
||||
return new(_tag);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
var state = GetUserInterfaceState();
|
||||
UserInterface?.SetState(state);
|
||||
}
|
||||
|
||||
private void ClickSound()
|
||||
{
|
||||
SoundSystem.Play(_clickSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default.WithVolume(-2f));
|
||||
@@ -90,10 +75,5 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
base.OnRemove();
|
||||
UserInterface?.CloseAll();
|
||||
}
|
||||
public void OpenUserInterface(ActorComponent actor)
|
||||
{
|
||||
UpdateUserInterface();
|
||||
UserInterface?.Open(actor.PlayerSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Disposal.Unit.Components;
|
||||
using Content.Server.Disposal.Unit.Components;
|
||||
|
||||
namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
@@ -9,6 +9,8 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
[ComponentReference(typeof(DisposalTubeComponent))]
|
||||
public class DisposalTransitComponent : DisposalTubeComponent
|
||||
{
|
||||
public override string ContainerId => "DisposalTransit";
|
||||
|
||||
protected override Direction[] ConnectableDirections()
|
||||
{
|
||||
var rotation = IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(Owner).LocalRotation;
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
public abstract class DisposalTubeComponent : Component, IDisposalTubeComponent
|
||||
{
|
||||
public virtual string ContainerId => "DisposalTube";
|
||||
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public static readonly TimeSpan ClangDelay = TimeSpan.FromSeconds(0.5);
|
||||
@@ -135,7 +137,7 @@ namespace Content.Server.Disposal.Tube.Components
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Contents = ContainerHelpers.EnsureContainer<Container>(Owner, Name);
|
||||
Contents = ContainerHelpers.EnsureContainer<Container>(Owner, ContainerId);
|
||||
Owner.EnsureComponent<AnchorableComponent>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Text;
|
||||
using Content.Server.Disposal.Tube.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.Movement;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -60,9 +62,11 @@ namespace Content.Server.Disposal.Tube
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
UpdateRouterUserInterface(router);
|
||||
}
|
||||
|
||||
private void OnOpenTaggerUIAttempt(EntityUid uid, DisposalTaggerComponent router, ActivatableUIOpenAttemptEvent args)
|
||||
private void OnOpenTaggerUIAttempt(EntityUid uid, DisposalTaggerComponent tagger, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (!TryComp<HandsComponent>(args.User, out var hands))
|
||||
{
|
||||
@@ -75,8 +79,34 @@ namespace Content.Server.Disposal.Tube
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
tagger.UserInterface?.SetState(new SharedDisposalTaggerComponent.DisposalTaggerUserInterfaceState(tagger.Tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets component data to be used to update the user interface client-side.
|
||||
/// </summary>
|
||||
/// <returns>Returns a <see cref="SharedDisposalRouterComponent.DisposalRouterUserInterfaceState"/></returns>
|
||||
private void UpdateRouterUserInterface(DisposalRouterComponent router)
|
||||
{
|
||||
if (router.Tags.Count <= 0)
|
||||
{
|
||||
router.UserInterface?.SetState(new SharedDisposalRouterComponent.DisposalRouterUserInterfaceState(""));
|
||||
return;
|
||||
}
|
||||
|
||||
var taglist = new StringBuilder();
|
||||
|
||||
foreach (var tag in router.Tags)
|
||||
{
|
||||
taglist.Append(tag);
|
||||
taglist.Append(", ");
|
||||
}
|
||||
|
||||
taglist.Remove(taglist.Length - 2, 2);
|
||||
|
||||
router.UserInterface?.SetState(new SharedDisposalRouterComponent.DisposalRouterUserInterfaceState(taglist.ToString()));
|
||||
}
|
||||
|
||||
private static void BodyTypeChanged(
|
||||
EntityUid uid,
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly DumpableSystem _dumpableSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
|
||||
private readonly List<DisposalUnitComponent> _activeDisposals = new();
|
||||
|
||||
@@ -293,7 +294,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
|
||||
|
||||
private void HandleDisposalInit(EntityUid uid, DisposalUnitComponent component, ComponentInit args)
|
||||
{
|
||||
component.Container = component.Owner.EnsureContainer<Container>(component.Name);
|
||||
component.Container = _containerSystem.EnsureContainer<Container>(uid, SharedDisposalUnitComponent.ContainerId);
|
||||
|
||||
UpdateInterface(component, component.Powered);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
|
||||
[Dependency] private readonly ConstructionSystem _constructionSystem = default!;
|
||||
[Dependency] private readonly ToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -94,11 +95,12 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
/// Selectively send sound to clients, taking care to not send the double-audio.
|
||||
/// </summary>
|
||||
/// <param name="uid">The audio source</param>
|
||||
/// <param name="sound">The sound</param>
|
||||
/// <param name="soundSpecifier">The sound</param>
|
||||
/// <param name="audioParams">The audio parameters.</param>
|
||||
/// <param name="predictingPlayer">The user (if any) that instigated an interaction</param>
|
||||
/// <param name="predicted">Whether this interaction would have been predicted. If the predicting player is null,
|
||||
/// this assumes it would have been predicted by all players in PVS range.</param>
|
||||
protected override void PlaySound(EntityUid uid, string sound, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
|
||||
protected override void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
|
||||
{
|
||||
// If this sound would have been predicted by all clients, do not play any audio.
|
||||
if (predicted && predictingPlayer == null)
|
||||
@@ -113,7 +115,7 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
}
|
||||
|
||||
// send the sound to players.
|
||||
SoundSystem.Play(sound, filter, uid, audioParams);
|
||||
Audio.Play(soundSpecifier, filter, uid, audioParams);
|
||||
}
|
||||
|
||||
#region DoAfters
|
||||
@@ -158,7 +160,11 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
|
||||
private void OnDoorAltVerb(EntityUid uid, DoorComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !TryComp<ToolComponent>(args.User, out var tool) || !tool.Qualities.Contains(component.PryingQuality)) return;
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
if (!TryComp<ToolComponent>(args.User, out var tool) || !tool.Qualities.Contains(component.PryingQuality))
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new AlternativeVerb()
|
||||
{
|
||||
@@ -173,7 +179,8 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
/// </summary>
|
||||
private bool TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door, bool force = false)
|
||||
{
|
||||
if (door.BeingPried) return false;
|
||||
if (door.BeingPried)
|
||||
return false;
|
||||
|
||||
if (door.State == DoorState.Welded)
|
||||
return false;
|
||||
@@ -274,9 +281,9 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
if (string.IsNullOrEmpty(door.BoardPrototype))
|
||||
return;
|
||||
|
||||
var container = uid.EnsureContainer<Container>("board", out var existed);
|
||||
var container = _containerSystem.EnsureContainer<Container>(uid, "board", out var existed);
|
||||
|
||||
if (existed & container.ContainedEntities.Count != 0)
|
||||
if (existed && container.ContainedEntities.Count != 0)
|
||||
{
|
||||
// We already contain a board. Note: We don't check if it's the right one!
|
||||
return;
|
||||
@@ -298,7 +305,7 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
if (door.State == DoorState.Closed)
|
||||
{
|
||||
SetState(uid, DoorState.Emagging, door);
|
||||
PlaySound(uid, door.SparkSound.GetSound(), AudioParams.Default.WithVolume(8), args.UserUid, false);
|
||||
PlaySound(uid, door.SparkSound, AudioParams.Default.WithVolume(8), args.UserUid, false);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -309,12 +316,12 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
if (!Resolve(uid, ref door))
|
||||
return;
|
||||
|
||||
DoorState lastState = door.State;
|
||||
var lastState = door.State;
|
||||
|
||||
SetState(uid, DoorState.Opening, door);
|
||||
|
||||
if (door.OpenSound != null)
|
||||
PlaySound(uid, door.OpenSound.GetSound(), AudioParams.Default.WithVolume(-5), user, predicted);
|
||||
PlaySound(uid, door.OpenSound, AudioParams.Default.WithVolume(-5), user, predicted);
|
||||
|
||||
if(lastState == DoorState.Emagging && TryComp<AirlockComponent>(door.Owner, out var airlockComponent))
|
||||
airlockComponent?.SetBoltsWithAudio(!airlockComponent.IsBolted());
|
||||
|
||||
@@ -20,6 +20,8 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Server.Traitor;
|
||||
using System.Data;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -35,6 +37,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
|
||||
private Dictionary<Mind.Mind, bool> _aliveNukeops = new();
|
||||
private bool _opsWon;
|
||||
|
||||
@@ -8,7 +8,6 @@ using Content.Server.Station.Components;
|
||||
using Content.Server.Suspicion;
|
||||
using Content.Server.Suspicion.Roles;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.EntityList;
|
||||
@@ -17,7 +16,6 @@ using Content.Shared.Maps;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Suspicion;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -48,6 +46,7 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
|
||||
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
|
||||
public override string Prototype => "Suspicion";
|
||||
|
||||
@@ -173,16 +172,8 @@ public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
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(traitorStartingBalance, mind.OwnedEntity!);
|
||||
var accounts = EntityManager.EntitySysManager.GetEntitySystem<UplinkAccountsSystem>();
|
||||
accounts.AddNewAccount(uplinkAccount);
|
||||
|
||||
// try to place uplink
|
||||
if (!EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
|
||||
.AddUplink(mind.OwnedEntity!.Value, uplinkAccount))
|
||||
if (!_uplink.AddUplink(mind.OwnedEntity!.Value, traitorStartingBalance))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking.Rules.Configurations;
|
||||
@@ -6,19 +6,18 @@ using Content.Server.Hands.Components;
|
||||
using Content.Server.PDA;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Traitor;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Server.Traitor.Uplink.Components;
|
||||
using Content.Server.TraitorDeathMatch.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -39,6 +38,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
|
||||
public override string Prototype => "TraitorDeathMatch";
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
|
||||
private bool _safeToEndRound = false;
|
||||
|
||||
private readonly Dictionary<UplinkAccount, string> _allOriginalNames = new();
|
||||
private readonly Dictionary<EntityUid, string> _allOriginalNames = new();
|
||||
|
||||
private const string TraitorPrototypeID = "Traitor";
|
||||
|
||||
@@ -108,15 +108,10 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
newTmp = Spawn(BackpackPrototypeName, ownedCoords);
|
||||
_inventory.TryEquip(owned, newTmp, "back", true);
|
||||
|
||||
// Like normal traitors, they need access to a traitor account.
|
||||
var uplinkAccount = new UplinkAccount(startingBalance, owned);
|
||||
var accounts = EntityManager.EntitySysManager.GetEntitySystem<UplinkAccountsSystem>();
|
||||
accounts.AddNewAccount(uplinkAccount);
|
||||
if (!_uplink.AddUplink(owned, startingBalance))
|
||||
return;
|
||||
|
||||
EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
|
||||
.AddUplink(owned, uplinkAccount, newPDA);
|
||||
|
||||
_allOriginalNames[uplinkAccount] = Name(owned);
|
||||
_allOriginalNames[owned] = Name(owned);
|
||||
|
||||
// The PDA needs to be marked with the correct owner.
|
||||
var pda = Comp<PDAComponent>(newPDA);
|
||||
@@ -186,14 +181,17 @@ public sealed class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
|
||||
var lines = new List<string>();
|
||||
lines.Add(Loc.GetString("traitor-death-match-end-round-description-first-line"));
|
||||
foreach (var uplink in EntityManager.EntityQuery<UplinkComponent>(true))
|
||||
|
||||
foreach (var uplink in EntityManager.EntityQuery<StoreComponent>(true))
|
||||
{
|
||||
var uplinkAcc = uplink.UplinkAccount;
|
||||
if (uplinkAcc != null && _allOriginalNames.ContainsKey(uplinkAcc))
|
||||
var owner = uplink.AccountOwner;
|
||||
if (owner != null && _allOriginalNames.ContainsKey(owner.Value))
|
||||
{
|
||||
var tcbalance = _uplink.GetTCBalance(uplink);
|
||||
|
||||
lines.Add(Loc.GetString("traitor-death-match-end-round-description-entry",
|
||||
("originalName", _allOriginalNames[uplinkAcc]),
|
||||
("tcBalance", uplinkAcc.Balance)));
|
||||
("originalName", _allOriginalNames[owner.Value]),
|
||||
("tcBalance", tcbalance)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ using Content.Server.Chat.Managers;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Server.Traitor;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -28,6 +29,10 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
[Dependency] private readonly IObjectivesManager _objectivesManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
|
||||
public override string Prototype => "Traitor";
|
||||
|
||||
@@ -35,6 +40,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
public List<TraitorRole> Traitors = new();
|
||||
|
||||
private const string TraitorPrototypeID = "Traitor";
|
||||
private const string TraitorUplinkPresetId = "StorePresetUplink";
|
||||
|
||||
public int TotalTraitors => Traitors.Count;
|
||||
public string[] Codewords = new string[3];
|
||||
@@ -173,16 +179,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem
|
||||
}
|
||||
|
||||
// creadth: we need to create uplink for the antag.
|
||||
// PDA should be in place already, so we just need to
|
||||
// initiate uplink account.
|
||||
// PDA should be in place already
|
||||
DebugTools.AssertNotNull(mind.OwnedEntity);
|
||||
|
||||
var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance);
|
||||
var uplinkAccount = new UplinkAccount(startingBalance, mind.OwnedEntity!);
|
||||
var accounts = EntityManager.EntitySysManager.GetEntitySystem<UplinkAccountsSystem>();
|
||||
accounts.AddNewAccount(uplinkAccount);
|
||||
|
||||
if (!EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>().AddUplink(mind.OwnedEntity!.Value, uplinkAccount))
|
||||
if (!_uplink.AddUplink(mind.OwnedEntity!.Value, startingBalance))
|
||||
return false;
|
||||
|
||||
var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorPrototypeID);
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed class RenameCommand : IConsoleCommand
|
||||
{
|
||||
foreach (var idCardComponent in entMan.EntityQuery<IdCardComponent>())
|
||||
{
|
||||
if (idCardComponent.OriginalOwnerName != oldName)
|
||||
if (idCardComponent.OriginalEntityName != oldName)
|
||||
continue;
|
||||
idCardSystem.TryChangeFullName(idCardComponent.Owner, name, idCardComponent);
|
||||
}
|
||||
|
||||
@@ -182,9 +182,11 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
|
||||
UpdateAppearance(component);
|
||||
|
||||
// Synchronize solution in drink
|
||||
EnsureComp<RefillableSolutionComponent>(uid).Solution = component.SolutionName;
|
||||
EnsureComp<DrainableSolutionComponent>(uid).Solution = component.SolutionName;
|
||||
if (TryComp(uid, out RefillableSolutionComponent? refillComp))
|
||||
refillComp.Solution = component.SolutionName;
|
||||
|
||||
if (TryComp(uid, out DrainableSolutionComponent? drainComp))
|
||||
drainComp.Solution = component.SolutionName;
|
||||
}
|
||||
|
||||
private void OnSolutionChange(EntityUid uid, DrinkComponent component, SolutionChangedEvent args)
|
||||
|
||||
@@ -30,8 +30,6 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
|
||||
public void CheckSolutions(TrashOnEmptyComponent component)
|
||||
{
|
||||
EntityManager.EnsureComponent<TagComponent>(component.Owner);
|
||||
|
||||
if (!EntityManager.HasComponent<SolutionContainerManagerComponent>((component).Owner))
|
||||
return;
|
||||
|
||||
|
||||
@@ -2,29 +2,28 @@ using Content.Server.Instruments;
|
||||
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.Account;
|
||||
using Content.Server.Traitor.Uplink.Components;
|
||||
using Content.Server.PDA.Ringer;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Traitor;
|
||||
|
||||
namespace Content.Server.PDA
|
||||
{
|
||||
public sealed class PDASystem : SharedPDASystem
|
||||
{
|
||||
[Dependency] private readonly UplinkSystem _uplinkSystem = default!;
|
||||
[Dependency] private readonly UplinkAccountsSystem _uplinkAccounts = default!;
|
||||
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
|
||||
[Dependency] private readonly RingerSystem _ringerSystem = default!;
|
||||
[Dependency] private readonly InstrumentSystem _instrumentSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly StoreSystem _storeSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -32,8 +31,8 @@ namespace Content.Server.PDA
|
||||
|
||||
SubscribeLocalEvent<PDAComponent, LightToggleEvent>(OnLightToggle);
|
||||
SubscribeLocalEvent<PDAComponent, AfterActivatableUIOpenEvent>(AfterUIOpen);
|
||||
SubscribeLocalEvent<PDAComponent, UplinkInitEvent>(OnUplinkInit);
|
||||
SubscribeLocalEvent<PDAComponent, UplinkRemovedEvent>(OnUplinkRemoved);
|
||||
SubscribeLocalEvent<PDAComponent, StoreAddedEvent>(OnUplinkInit);
|
||||
SubscribeLocalEvent<PDAComponent, StoreRemovedEvent>(OnUplinkRemoved);
|
||||
SubscribeLocalEvent<PDAComponent, GridModifiedEvent>(OnGridChanged);
|
||||
}
|
||||
|
||||
@@ -74,12 +73,12 @@ namespace Content.Server.PDA
|
||||
UpdatePDAUserInterface(pda);
|
||||
}
|
||||
|
||||
private void OnUplinkInit(EntityUid uid, PDAComponent pda, UplinkInitEvent args)
|
||||
private void OnUplinkInit(EntityUid uid, PDAComponent pda, StoreAddedEvent args)
|
||||
{
|
||||
UpdatePDAUserInterface(pda);
|
||||
}
|
||||
|
||||
private void OnUplinkRemoved(EntityUid uid, PDAComponent pda, UplinkRemovedEvent args)
|
||||
private void OnUplinkRemoved(EntityUid uid, PDAComponent pda, StoreRemovedEvent args)
|
||||
{
|
||||
UpdatePDAUserInterface(pda);
|
||||
}
|
||||
@@ -111,7 +110,7 @@ namespace Content.Server.PDA
|
||||
// players. This should really use a sort of key-code entry system that selects an account which is not directly tied to
|
||||
// a player entity.
|
||||
|
||||
if (!HasComp<UplinkComponent>(pda.Owner))
|
||||
if (!TryComp<StoreComponent>(pda.Owner, out var storeComponent))
|
||||
return;
|
||||
|
||||
var uplinkState = new PDAUpdateState(pda.FlashlightOn, pda.PenSlot.HasItem, ownerInfo, pda.StationName, true, hasInstrument);
|
||||
@@ -121,7 +120,8 @@ namespace Content.Server.PDA
|
||||
if (session.AttachedEntity is not EntityUid { Valid: true } user)
|
||||
continue;
|
||||
|
||||
if (_uplinkAccounts.HasAccount(user))
|
||||
if (storeComponent.AccountOwner == user || (TryComp<MindComponent>(session.AttachedEntity, out var mindcomp) && mindcomp.Mind != null &&
|
||||
mindcomp.Mind.HasRole<TraitorRole>()))
|
||||
ui.SetState(uplinkState, session);
|
||||
}
|
||||
}
|
||||
@@ -143,8 +143,9 @@ namespace Content.Server.PDA
|
||||
|
||||
case PDAShowUplinkMessage _:
|
||||
{
|
||||
if (EntityManager.TryGetComponent(pda.Owner, out UplinkComponent? uplink))
|
||||
_uplinkSystem.ToggleUplinkUI(uplink, msg.Session);
|
||||
if (msg.Session.AttachedEntity != null &&
|
||||
TryComp<StoreComponent>(pda.Owner, out var store))
|
||||
_storeSystem.ToggleUi(msg.Session.AttachedEntity.Value, store);
|
||||
break;
|
||||
}
|
||||
case PDAShowRingtoneMessage _:
|
||||
@@ -170,8 +171,13 @@ namespace Content.Server.PDA
|
||||
|
||||
private void AfterUIOpen(EntityUid uid, PDAComponent pda, AfterActivatableUIOpenEvent args)
|
||||
{
|
||||
//TODO: this is awful
|
||||
// A new user opened the UI --> Check if they are a traitor and should get a user specific UI state override.
|
||||
if (!HasComp<UplinkComponent>(pda.Owner) || !_uplinkAccounts.HasAccount(args.User))
|
||||
if (!TryComp<StoreComponent>(pda.Owner, out var storeComp))
|
||||
return;
|
||||
|
||||
if (storeComp.AccountOwner != args.User &&
|
||||
!(TryComp<MindComponent>(args.User, out var mindcomp) && mindcomp.Mind != null && mindcomp.Mind.HasRole<TraitorRole>()))
|
||||
return;
|
||||
|
||||
if (!_uiSystem.TryGetUi(pda.Owner, PDAUiKey.Key, out var ui))
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Content.Server.PDA.Ringer
|
||||
base.Initialize();
|
||||
|
||||
// General Event Subscriptions
|
||||
SubscribeLocalEvent<RingerComponent, ComponentInit>(RandomizeRingtone);
|
||||
SubscribeLocalEvent<RingerComponent, MapInitEvent>(RandomizeRingtone);
|
||||
// RingerBoundUserInterface Subscriptions
|
||||
SubscribeLocalEvent<RingerComponent, RingerSetRingtoneMessage>(OnSetRingtone);
|
||||
SubscribeLocalEvent<RingerComponent, RingerPlayRingtoneMessage>(RingerPlayRingtone);
|
||||
@@ -46,7 +46,7 @@ namespace Content.Server.PDA.Ringer
|
||||
UpdateRingerRingtone(ringer, args.Ringtone);
|
||||
}
|
||||
|
||||
public void RandomizeRingtone(EntityUid uid, RingerComponent ringer, ComponentInit args)
|
||||
public void RandomizeRingtone(EntityUid uid, RingerComponent ringer, MapInitEvent args)
|
||||
{
|
||||
// Default to using C pentatonic so it at least sounds not terrible.
|
||||
var notes = new[]
|
||||
|
||||
@@ -191,7 +191,12 @@ namespace Content.Server.ParticleAccelerator.Components
|
||||
|
||||
protected override void OnRemove()
|
||||
{
|
||||
UserInterface?.CloseAll();
|
||||
Master = null;
|
||||
foreach (var part in AllParts())
|
||||
{
|
||||
part.Master = null;
|
||||
}
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
@@ -331,12 +336,12 @@ namespace Content.Server.ParticleAccelerator.Components
|
||||
_partEmitterCenter = null;
|
||||
_partEmitterRight = null;
|
||||
|
||||
var xform = _entMan.GetComponent<TransformComponent>(Owner);
|
||||
|
||||
// Find fuel chamber first by scanning cardinals.
|
||||
if (_entMan.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
if (xform.Anchored && _entMan.TryGetComponent(xform.GridUid, out IMapGridComponent? grid))
|
||||
{
|
||||
var grid = _mapManager.GetGrid(_entMan.GetComponent<TransformComponent>(Owner).GridUid!.Value);
|
||||
var coords = _entMan.GetComponent<TransformComponent>(Owner).Coordinates;
|
||||
foreach (var maybeFuel in grid.GetCardinalNeighborCells(coords))
|
||||
foreach (var maybeFuel in grid.Grid.GetCardinalNeighborCells(xform.Coordinates))
|
||||
{
|
||||
if (_entMan.TryGetComponent(maybeFuel, out _partFuelChamber))
|
||||
{
|
||||
@@ -354,7 +359,7 @@ namespace Content.Server.ParticleAccelerator.Components
|
||||
// Align ourselves to match fuel chamber orientation.
|
||||
// This means that if you mess up the orientation of the control box it's not a big deal,
|
||||
// because the sprite is far from obvious about the orientation.
|
||||
_entMan.GetComponent<TransformComponent>(Owner).LocalRotation = _entMan.GetComponent<TransformComponent>(_partFuelChamber.Owner).LocalRotation;
|
||||
xform.LocalRotation = _entMan.GetComponent<TransformComponent>(_partFuelChamber.Owner).LocalRotation;
|
||||
|
||||
var offsetEndCap = RotateOffset((1, 1));
|
||||
var offsetPowerBox = RotateOffset((1, -1));
|
||||
|
||||
@@ -124,7 +124,6 @@ public sealed class PlayTimeTrackingManager
|
||||
if (data.NeedRefreshTackers)
|
||||
{
|
||||
RefreshSingleTracker(player, data, time);
|
||||
data.NeedRefreshTackers = true;
|
||||
}
|
||||
|
||||
if (data.NeedSendTimers)
|
||||
|
||||
@@ -23,7 +23,9 @@ using Content.Shared.MobState;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using System.Linq;
|
||||
using Content.Server.Emag;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Shared.CharacterAppearance.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Revenant.EntitySystems;
|
||||
@@ -177,7 +179,9 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
|
||||
essence.Harvested = true;
|
||||
ChangeEssenceAmount(uid, essence.EssenceAmount, component);
|
||||
component.StolenEssence += essence.EssenceAmount;
|
||||
if (TryComp<StoreComponent>(uid, out var store))
|
||||
_store.TryAddCurrency(new Dictionary<string, FixedPoint2>()
|
||||
{ {component.StolenEssenceCurrencyPrototype, essence.EssenceAmount} }, store);
|
||||
|
||||
if (!TryComp<MobStateComponent>(args.Target, out var mobstate))
|
||||
return;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Revenant;
|
||||
using Content.Server.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
|
||||
namespace Content.Server.Revenant.EntitySystems;
|
||||
|
||||
// TODO: Delete and replace all of this with StoreSystem once that's merged
|
||||
// i'm sorry, but i'm not ultra-optimizing something that's getting deleted in a week.
|
||||
// 8/7/22 -emo (bully me if this exists in the future)
|
||||
public sealed partial class RevenantSystem : EntitySystem
|
||||
{
|
||||
private void InitializeShop()
|
||||
{
|
||||
SubscribeLocalEvent<RevenantComponent, RevenantShopActionEvent>(OnShop);
|
||||
SubscribeLocalEvent<RevenantComponent, RevenantBuyListingMessage>(OnBuy);
|
||||
}
|
||||
|
||||
private void OnShop(EntityUid uid, RevenantComponent component, RevenantShopActionEvent args)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(uid, out var actor))
|
||||
return;
|
||||
ToggleUi(component, actor.PlayerSession);
|
||||
}
|
||||
|
||||
private void OnBuy(EntityUid uid, RevenantComponent component, RevenantBuyListingMessage ev)
|
||||
{
|
||||
RevenantStoreListingPrototype? targetListing = null;
|
||||
foreach (var listing in component.Listings)
|
||||
{
|
||||
if (listing.Key.ID == ev.Listing.ID)
|
||||
targetListing = listing.Key;
|
||||
}
|
||||
|
||||
if (targetListing == null)
|
||||
return;
|
||||
component.Listings[targetListing] = false;
|
||||
|
||||
if (component.StolenEssence < ev.Listing.Price)
|
||||
return;
|
||||
component.StolenEssence -= ev.Listing.Price;
|
||||
|
||||
if (_proto.TryIndex<InstantActionPrototype>(ev.Listing.ActionId, out var action))
|
||||
_action.AddAction(uid, new InstantAction(action), null);
|
||||
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
public void ToggleUi(RevenantComponent component, IPlayerSession session)
|
||||
{
|
||||
var ui = component.Owner.GetUIOrNull(RevenantUiKey.Key);
|
||||
ui?.Toggle(session);
|
||||
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(RevenantComponent component)
|
||||
{
|
||||
var ui = component.Owner.GetUIOrNull(RevenantUiKey.Key);
|
||||
if (ui == null)
|
||||
return;
|
||||
|
||||
var filterlistings = (from e in component.Listings where e.Value select e.Key).ToList();
|
||||
|
||||
ui.SetState(new RevenantUpdateState(component.StolenEssence.Float(), filterlistings));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Movement.Systems;
|
||||
@@ -42,6 +44,7 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -49,13 +52,13 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<RevenantComponent, ComponentStartup>(OnStartup);
|
||||
|
||||
SubscribeLocalEvent<RevenantComponent, RevenantShopActionEvent>(OnShop);
|
||||
SubscribeLocalEvent<RevenantComponent, DamageChangedEvent>(OnDamage);
|
||||
SubscribeLocalEvent<RevenantComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<RevenantComponent, StatusEffectAddedEvent>(OnStatusAdded);
|
||||
SubscribeLocalEvent<RevenantComponent, StatusEffectEndedEvent>(OnStatusEnded);
|
||||
|
||||
InitializeAbilities();
|
||||
InitializeShop();
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, RevenantComponent component, ComponentStartup args)
|
||||
@@ -72,10 +75,6 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
if (TryComp(component.Owner, out EyeComponent? eye))
|
||||
eye.VisibilityMask |= (uint) (VisibilityFlags.Ghost);
|
||||
|
||||
//get all the abilities
|
||||
foreach (var listing in _proto.EnumeratePrototypes<RevenantStoreListingPrototype>())
|
||||
component.Listings.Add(listing, true);
|
||||
|
||||
var shopaction = new InstantAction(_proto.Index<InstantActionPrototype>("RevenantShop"));
|
||||
_action.AddAction(uid, shopaction, null);
|
||||
}
|
||||
@@ -133,7 +132,8 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
_polymorphable.PolymorphEntity(uid, "Ectoplasm");
|
||||
}
|
||||
|
||||
UpdateUserInterface(component);
|
||||
if (TryComp<StoreComponent>(uid, out var store))
|
||||
_store.UpdateUserInterface(uid, store);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -163,6 +163,13 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnShop(EntityUid uid, RevenantComponent component, RevenantShopActionEvent args)
|
||||
{
|
||||
if (!TryComp<StoreComponent>(uid, out var store))
|
||||
return;
|
||||
_store.ToggleUi(uid, store);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using System.Threading;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
|
||||
namespace Content.Server.Revenant;
|
||||
|
||||
@@ -17,11 +18,8 @@ public sealed class RevenantComponent : SharedRevenantComponent
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2 Essence = 75;
|
||||
|
||||
/// <summary>
|
||||
/// Used for purchasing shop items.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2 StolenEssence = 0;
|
||||
[DataField("stolenEssenceCurrencyPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<CurrencyPrototype>))]
|
||||
public string StolenEssenceCurrencyPrototype = "StolenEssence";
|
||||
|
||||
/// <summary>
|
||||
/// The entity's current max amount of essence. Can be increased
|
||||
@@ -189,12 +187,6 @@ public sealed class RevenantComponent : SharedRevenantComponent
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("malfunctionRadius")]
|
||||
public float MalfunctionRadius = 3.5f;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Stores all of the currently unlockable abilities in the shop.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<RevenantStoreListingPrototype, bool> Listings = new ();
|
||||
}
|
||||
|
||||
public sealed class SoulSearchDoAfterComplete : EntityEventArgs
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Station.Components;
|
||||
@@ -61,6 +59,8 @@ public sealed class StationSystem : EntitySystem
|
||||
SubscribeLocalEvent<StationDataComponent, ComponentAdd>(OnStationAdd);
|
||||
SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted);
|
||||
SubscribeLocalEvent<StationDataComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<StationMemberComponent, ComponentShutdown>(OnStationGridDeleted);
|
||||
SubscribeLocalEvent<StationMemberComponent, PostGridSplitEvent>(OnStationSplitEvent);
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.StationOffset, x => _randomStationOffset = x, true);
|
||||
_configurationManager.OnValueChanged(CCVars.MaxStationOffset, x => _maxRandomStationOffset = x, true);
|
||||
@@ -69,6 +69,22 @@ public sealed class StationSystem : EntitySystem
|
||||
_player.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnStationSplitEvent(EntityUid uid, StationMemberComponent component, ref PostGridSplitEvent args)
|
||||
{
|
||||
AddGridToStation(component.Station, args.Grid); // Add the new grid as a member.
|
||||
}
|
||||
|
||||
private void OnStationGridDeleted(EntityUid uid, StationMemberComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<StationDataComponent>(component.Station, out var stationData))
|
||||
return;
|
||||
|
||||
stationData.Grids.Remove(uid);
|
||||
|
||||
// TODO: Remove this when we find out what's mysteriously pulling the rug under us maps wise.
|
||||
_sawmill.Debug($"Station grid is being deleted, trace is: {Environment.StackTrace}");
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
@@ -109,6 +125,11 @@ public sealed class StationSystem : EntitySystem
|
||||
Logger.Error($"Station entity {ToPrettyString(uid)} is getting deleted mid-round. Trace: {Environment.StackTrace}");
|
||||
}
|
||||
|
||||
foreach (var grid in component.Grids)
|
||||
{
|
||||
RemComp<StationMemberComponent>(grid);
|
||||
}
|
||||
|
||||
_stations.Remove(uid);
|
||||
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast());
|
||||
}
|
||||
@@ -175,14 +196,12 @@ public sealed class StationSystem : EntitySystem
|
||||
if (!dict.Any())
|
||||
{
|
||||
// Oh jeez, no stations got loaded.
|
||||
// We'll just take the first grid and setup that, then.
|
||||
|
||||
var grid = ev.Grids[0];
|
||||
|
||||
AddGrid("Station", grid);
|
||||
// We'll yell about it, but the thing this used to do with creating a dummy is kinda pointless now.
|
||||
_sawmill.Error($"There were no station grids for {ev.GameMap.ID}!");
|
||||
}
|
||||
|
||||
// Iterate over all PartOfStation
|
||||
// TODO: Remove this whenever pillar finally gets replaced. It's the sole user.
|
||||
foreach (var grid in ev.Grids)
|
||||
{
|
||||
if (!TryComp<PartOfStationComponent>(grid, out var partOfStation))
|
||||
@@ -204,11 +223,12 @@ public sealed class StationSystem : EntitySystem
|
||||
|
||||
private void OnRoundEnd(GameRunLevelChangedEvent eventArgs)
|
||||
{
|
||||
if (eventArgs.New != GameRunLevel.PreRoundLobby) return;
|
||||
if (eventArgs.New != GameRunLevel.PreRoundLobby)
|
||||
return;
|
||||
|
||||
foreach (var entity in _stations)
|
||||
{
|
||||
Del(entity);
|
||||
DeleteStation(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +255,13 @@ public sealed class StationSystem : EntitySystem
|
||||
return largestGrid;
|
||||
}
|
||||
|
||||
public Filter GetInStation(EntityUid source, float range = 32f)
|
||||
/// <summary>
|
||||
/// Tries to retrieve a filter for everything in the station the source is on.
|
||||
/// </summary>
|
||||
/// <param name="source">The entity to use to find the station.</param>
|
||||
/// <param name="range">The range around the station</param>
|
||||
/// <returns></returns>
|
||||
public Filter GetInOwningStation(EntityUid source, float range = 32f)
|
||||
{
|
||||
var station = GetOwningStation(source);
|
||||
|
||||
@@ -261,7 +287,8 @@ public sealed class StationSystem : EntitySystem
|
||||
foreach (var gridUid in dataComponent.Grids)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(gridUid, out var grid) ||
|
||||
!xformQuery.TryGetComponent(gridUid, out var xform)) continue;
|
||||
!xformQuery.TryGetComponent(gridUid, out var xform))
|
||||
continue;
|
||||
|
||||
var mapId = xform.MapID;
|
||||
var position = _transform.GetWorldPosition(xform, xformQuery);
|
||||
@@ -277,17 +304,20 @@ public sealed class StationSystem : EntitySystem
|
||||
foreach (var session in Filter.GetAllPlayers(_player))
|
||||
{
|
||||
var entity = session.AttachedEntity;
|
||||
if (entity == null || !xformQuery.TryGetComponent(entity, out var xform)) continue;
|
||||
if (entity == null || !xformQuery.TryGetComponent(entity, out var xform))
|
||||
continue;
|
||||
|
||||
var mapId = xform.MapID;
|
||||
|
||||
if (!mapIds.Contains(mapId)) continue;
|
||||
if (!mapIds.Contains(mapId))
|
||||
continue;
|
||||
|
||||
var position = _transform.GetWorldPosition(xform, xformQuery);
|
||||
|
||||
foreach (var bound in bounds)
|
||||
{
|
||||
if (!bound.Contains(position)) continue;
|
||||
if (!bound.Contains(position))
|
||||
continue;
|
||||
|
||||
filter.AddPlayer(session);
|
||||
break;
|
||||
@@ -355,6 +385,7 @@ public sealed class StationSystem : EntitySystem
|
||||
/// <param name="station">Station to attach the grid to.</param>
|
||||
/// <param name="gridComponent">Resolve pattern, grid component of mapGrid.</param>
|
||||
/// <param name="stationData">Resolve pattern, station data component of station.</param>
|
||||
/// <param name="name">The name to assign to the grid if any.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when mapGrid or station are not a grid or station, respectively.</exception>
|
||||
public void AddGridToStation(EntityUid station, EntityUid mapGrid, IMapGridComponent? gridComponent = null, StationDataComponent? stationData = null, string? name = null)
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class EntityStorageSystem : EntitySystem
|
||||
component.Contents.OccludesLight = component.OccludesLight;
|
||||
|
||||
if (TryComp<ConstructionComponent>(uid, out var construction))
|
||||
_construction.AddContainer(uid, nameof(EntityStorageComponent), construction);
|
||||
_construction.AddContainer(uid, ContainerName, construction);
|
||||
|
||||
if (TryComp<PlaceableSurfaceComponent>(uid, out var placeable))
|
||||
_placeableSurface.SetPlaceable(uid, component.Open, placeable);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Server.Storage.EntitySystems
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -31,7 +32,7 @@ namespace Content.Server.Storage.EntitySystems
|
||||
component.SecretPartName = entityName;
|
||||
}
|
||||
|
||||
component.ItemContainer = ContainerHelpers.EnsureContainer<ContainerSlot>(uid, "stash", out _);
|
||||
component.ItemContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, "stash", out _);
|
||||
}
|
||||
|
||||
private void OnDestroyed(EntityUid uid, SecretStashComponent component, DestructionEventArgs args)
|
||||
|
||||
@@ -8,7 +8,6 @@ public sealed partial class StorageSystem
|
||||
private void OnStorageFillMapInit(EntityUid uid, StorageFillComponent component, MapInitEvent args)
|
||||
{
|
||||
if (component.Contents.Count == 0) return;
|
||||
if (!EntityManager.EntitySysManager.TryGetEntitySystem<EntityStorageSystem>(out var entityStorage)) return;
|
||||
|
||||
TryComp<ServerStorageComponent>(uid, out var serverStorageComp);
|
||||
TryComp<EntityStorageComponent>(uid, out var entityStorageComp);
|
||||
@@ -27,10 +26,10 @@ public sealed partial class StorageSystem
|
||||
var ent = EntityManager.SpawnEntity(item, coordinates);
|
||||
|
||||
// handle depending on storage component, again this should be unified after ECS
|
||||
if (entityStorageComp != null && entityStorage.Insert(ent, uid))
|
||||
if (entityStorageComp != null && _entityStorage.Insert(ent, uid))
|
||||
continue;
|
||||
|
||||
if (serverStorageComp != null && Insert(uid, ent, serverStorageComp))
|
||||
if (serverStorageComp != null && Insert(uid, ent, serverStorageComp, false))
|
||||
continue;
|
||||
|
||||
Logger.ErrorS("storage", $"Tried to StorageFill {item} inside {ToPrettyString(uid)} but can't.");
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Content.Server.Storage.EntitySystems
|
||||
[Dependency] private readonly SharedInteractionSystem _sharedInteractionSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
@@ -529,9 +530,8 @@ namespace Content.Server.Storage.EntitySystems
|
||||
/// <summary>
|
||||
/// Inserts into the storage container
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to insert</param>
|
||||
/// <returns>true if the entity was inserted, false otherwise</returns>
|
||||
public bool Insert(EntityUid uid, EntityUid insertEnt, ServerStorageComponent? storageComp = null)
|
||||
public bool Insert(EntityUid uid, EntityUid insertEnt, ServerStorageComponent? storageComp = null, bool playSound = true)
|
||||
{
|
||||
if (!Resolve(uid, ref storageComp))
|
||||
return false;
|
||||
@@ -539,8 +539,10 @@ namespace Content.Server.Storage.EntitySystems
|
||||
if (!CanInsert(uid, insertEnt, out _, storageComp) || storageComp.Storage?.Insert(insertEnt) == false)
|
||||
return false;
|
||||
|
||||
if (storageComp.StorageInsertSound is not null)
|
||||
SoundSystem.Play(storageComp.StorageInsertSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, AudioParams.Default);
|
||||
if (playSound && storageComp.StorageInsertSound is not null)
|
||||
{
|
||||
_audio.PlayPvs(storageComp.StorageInsertSound, uid);
|
||||
}
|
||||
|
||||
RecalculateStorageUsed(storageComp);
|
||||
UpdateStorageUI(uid, storageComp);
|
||||
@@ -651,12 +653,12 @@ namespace Content.Server.Storage.EntitySystems
|
||||
DebugTools.Assert(storedStorageComp != storageComp, $"Storage component contains itself!? Entity: {uid}");
|
||||
}
|
||||
|
||||
if (TryComp(entity, out ServerUserInterfaceComponent? uiComponent))
|
||||
if (!TryComp(entity, out ServerUserInterfaceComponent? ui))
|
||||
continue;
|
||||
|
||||
foreach (var bui in ui.Interfaces.Values)
|
||||
{
|
||||
foreach (var ui in uiComponent.Interfaces)
|
||||
{
|
||||
ui.Close(session);
|
||||
}
|
||||
_uiSystem.TryClose(entity, bui.UiKey, session, ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
Content.Server/Store/Components/CurrencyComponent.cs
Normal file
22
Content.Server/Store/Components/CurrencyComponent.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Server.Store.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies a component that can be inserted into a store
|
||||
/// to increase its balance.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class CurrencyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The value of the currency.
|
||||
/// The string is the currency type that will be added.
|
||||
/// The FixedPoint2 is the value of each individual currency entity.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("price", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))]
|
||||
public Dictionary<string, FixedPoint2> Price = new();
|
||||
}
|
||||
91
Content.Server/Store/Components/StoreComponent.cs
Normal file
91
Content.Server/Store/Components/StoreComponent.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Store.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component manages a store which players can use to purchase different listings
|
||||
/// through the ui. The currency, listings, and categories are defined in yaml.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class StoreComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The default preset for the store. Is overriden by default values specified on the component.
|
||||
/// </summary>
|
||||
[DataField("preset", customTypeSerializer: typeof(PrototypeIdSerializer<StorePresetPrototype>))]
|
||||
public string? Preset;
|
||||
|
||||
/// <summary>
|
||||
/// All the listing categories that are available on this store.
|
||||
/// The available listings are partially based on the categories.
|
||||
/// </summary>
|
||||
[DataField("categories", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<StoreCategoryPrototype>))]
|
||||
public HashSet<string> Categories = new();
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of currency that can be used in the store.
|
||||
/// The string represents the ID of te currency prototype, where the
|
||||
/// float is that amount.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("balance", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, CurrencyPrototype>))]
|
||||
public Dictionary<string, FixedPoint2> Balance = new();
|
||||
|
||||
/// <summary>
|
||||
/// The list of currencies that can be inserted into this store.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly), DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<CurrencyPrototype>))]
|
||||
public HashSet<string> CurrencyWhitelist = new();
|
||||
|
||||
/// <summary>
|
||||
/// The person who "owns" the store/account. Used if you want the listings to be fixed
|
||||
/// regardless of who activated it. I.E. role specific items for uplinks.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? AccountOwner = null;
|
||||
|
||||
/// <summary>
|
||||
/// All listings, including those that aren't available to the buyer
|
||||
/// </summary>
|
||||
public HashSet<ListingData> Listings = new();
|
||||
|
||||
/// <summary>
|
||||
/// All available listings from the last time that it was checked.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<ListingData> LastAvailableListings = new();
|
||||
|
||||
/// <summary>
|
||||
/// checks whether or not the store has been opened yet.
|
||||
/// </summary>
|
||||
public bool Opened = false;
|
||||
|
||||
#region audio
|
||||
/// <summary>
|
||||
/// The sound played to the buyer when a purchase is succesfully made.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("buySuccessSound")]
|
||||
public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// The sound played to the buyer when a purchase fails.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("insufficientFundsSound")]
|
||||
public SoundSpecifier InsufficientFundsSound = new SoundPathSpecifier("/Audio/Effects/error.ogg");
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event that is broadcast when a store is added to an entity
|
||||
/// </summary>
|
||||
public sealed class StoreAddedEvent : EntityEventArgs { };
|
||||
/// <summary>
|
||||
/// Event that is broadcast when a store is removed from an entity
|
||||
/// </summary>
|
||||
public sealed class StoreRemovedEvent : EntityEventArgs { };
|
||||
63
Content.Server/Store/Conditions/BuyerAntagCondition.cs
Normal file
63
Content.Server/Store/Conditions/BuyerAntagCondition.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Traitor;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Shared.Store.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Allows a store entry to be filtered out based on the user's antag role.
|
||||
/// Supports both blacklists and whitelists. This is copypaste because roles
|
||||
/// are absolute shitcode. Refactor this later. -emo
|
||||
/// </summary>
|
||||
public sealed class BuyerAntagCondition : ListingCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// A whitelist of antag roles that can purchase this listing. Only one needs to be found.
|
||||
/// </summary>
|
||||
[DataField("whitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AntagPrototype>))]
|
||||
public HashSet<string>? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// A blacklist of antag roles that cannot purchase this listing. Only one needs to be found.
|
||||
/// </summary>
|
||||
[DataField("blacklist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AntagPrototype>))]
|
||||
public HashSet<string>? Blacklist;
|
||||
|
||||
public override bool Condition(ListingConditionArgs args)
|
||||
{
|
||||
var ent = args.EntityManager;
|
||||
|
||||
if (!ent.TryGetComponent<MindComponent>(args.Buyer, out var mind) || mind.Mind == null)
|
||||
return true;
|
||||
|
||||
if (Blacklist != null)
|
||||
{
|
||||
foreach (var role in mind.Mind.AllRoles)
|
||||
{
|
||||
if (role is not TraitorRole blacklistantag)
|
||||
continue;
|
||||
|
||||
if (Blacklist.Contains(blacklistantag.Prototype.ID))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Whitelist != null)
|
||||
{
|
||||
var found = false;
|
||||
foreach (var role in mind.Mind.AllRoles)
|
||||
{
|
||||
if (role is not TraitorRole antag)
|
||||
continue;
|
||||
|
||||
if (Whitelist.Contains(antag.Prototype.ID))
|
||||
found = true;
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
63
Content.Server/Store/Conditions/BuyerJobCondition.cs
Normal file
63
Content.Server/Store/Conditions/BuyerJobCondition.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Server.Store.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Allows a store entry to be filtered out based on the user's job.
|
||||
/// Supports both blacklists and whitelists
|
||||
/// </summary>
|
||||
public sealed class BuyerJobCondition : ListingCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// A whitelist of jobs prototypes that can purchase this listing. Only one needs to be found.
|
||||
/// </summary>
|
||||
[DataField("whitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<JobPrototype>))]
|
||||
public HashSet<string>? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// A blacklist of job prototypes that can purchase this listing. Only one needs to be found.
|
||||
/// </summary>
|
||||
[DataField("blacklist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<JobPrototype>))]
|
||||
public HashSet<string>? Blacklist;
|
||||
|
||||
public override bool Condition(ListingConditionArgs args)
|
||||
{
|
||||
var ent = args.EntityManager;
|
||||
|
||||
if (!ent.TryGetComponent<MindComponent>(args.Buyer, out var mind) || mind.Mind == null)
|
||||
return true; //this is for things like surplus crate
|
||||
|
||||
if (Blacklist != null)
|
||||
{
|
||||
foreach (var role in mind.Mind.AllRoles)
|
||||
{
|
||||
if (role is not Job job)
|
||||
continue;
|
||||
|
||||
if (Blacklist.Contains(job.Prototype.ID))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Whitelist != null)
|
||||
{
|
||||
var found = false;
|
||||
foreach (var role in mind.Mind.AllRoles)
|
||||
{
|
||||
if (role is not Job job)
|
||||
continue;
|
||||
|
||||
if (Whitelist.Contains(job.Prototype.ID))
|
||||
found = true;
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
41
Content.Server/Store/Conditions/BuyerWhitelistCondition.cs
Normal file
41
Content.Server/Store/Conditions/BuyerWhitelistCondition.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Store.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Filters out an entry based on the components or tags on an entity.
|
||||
/// </summary>
|
||||
public sealed class BuyerWhitelistCondition : ListingCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// A whitelist of tags or components.
|
||||
/// </summary>
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// A blacklist of tags or components.
|
||||
/// </summary>
|
||||
[DataField("blacklist")]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
public override bool Condition(ListingConditionArgs args)
|
||||
{
|
||||
var ent = args.EntityManager;
|
||||
|
||||
if (Whitelist != null)
|
||||
{
|
||||
if (!Whitelist.IsValid(args.Buyer, ent))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Blacklist != null)
|
||||
{
|
||||
if (Blacklist.IsValid(args.Buyer, ent))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Store;
|
||||
|
||||
namespace Content.Server.Store.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Only allows a listing to be purchased a certain amount of times.
|
||||
/// </summary>
|
||||
public sealed class ListingLimitedStockCondition : ListingCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of times this listing can be purchased.
|
||||
/// </summary>
|
||||
[DataField("stock", required: true)]
|
||||
public int Stock;
|
||||
|
||||
public override bool Condition(ListingConditionArgs args)
|
||||
{
|
||||
return args.Listing.PurchaseAmount < Stock;
|
||||
}
|
||||
}
|
||||
44
Content.Server/Store/Conditions/StoreWhitelistCondition.cs
Normal file
44
Content.Server/Store/Conditions/StoreWhitelistCondition.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Store.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Filters out an entry based on the components or tags on the store itself.
|
||||
/// </summary>
|
||||
public sealed class StoreWhitelistCondition : ListingCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// A whitelist of tags or components.
|
||||
/// </summary>
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// A blacklist of tags or components.
|
||||
/// </summary>
|
||||
[DataField("blacklist")]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
public override bool Condition(ListingConditionArgs args)
|
||||
{
|
||||
if (args.StoreEntity == null)
|
||||
return false;
|
||||
|
||||
var ent = args.EntityManager;
|
||||
|
||||
if (Whitelist != null)
|
||||
{
|
||||
if (!Whitelist.IsValid(args.StoreEntity.Value, ent))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Blacklist != null)
|
||||
{
|
||||
if (Blacklist.IsValid(args.StoreEntity.Value, ent))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
127
Content.Server/Store/Systems/StoreSystem.Listings.cs
Normal file
127
Content.Server/Store/Systems/StoreSystem.Listings.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Shared.Store;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
public sealed partial class StoreSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Refreshes all listings on a store.
|
||||
/// Do not use if you don't know what you're doing.
|
||||
/// </summary>
|
||||
/// <param name="component">The store to refresh</param>
|
||||
public void RefreshAllListings(StoreComponent component)
|
||||
{
|
||||
component.Listings = GetAllListings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all listings from a prototype.
|
||||
/// </summary>
|
||||
/// <returns>All the listings</returns>
|
||||
public HashSet<ListingData> GetAllListings()
|
||||
{
|
||||
var allListings = _proto.EnumeratePrototypes<ListingPrototype>();
|
||||
|
||||
var allData = new HashSet<ListingData>();
|
||||
|
||||
foreach (var listing in allListings)
|
||||
allData.Add(listing);
|
||||
|
||||
return allData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a listing from an Id to a store
|
||||
/// </summary>
|
||||
/// <param name="component">The store to add the listing to</param>
|
||||
/// <param name="listingId">The id of the listing</param>
|
||||
/// <returns>Whetehr or not the listing was added successfully</returns>
|
||||
public bool TryAddListing(StoreComponent component, string listingId)
|
||||
{
|
||||
if (!_proto.TryIndex<ListingPrototype>(listingId, out var proto))
|
||||
{
|
||||
Logger.Error("Attempted to add invalid listing.");
|
||||
return false;
|
||||
}
|
||||
return TryAddListing(component, proto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a listing to a store
|
||||
/// </summary>
|
||||
/// <param name="component">The store to add the listing to</param>
|
||||
/// <param name="listing">The listing</param>
|
||||
/// <returns>Whether or not the listing was add successfully</returns>
|
||||
public bool TryAddListing(StoreComponent component, ListingData listing)
|
||||
{
|
||||
return component.Listings.Add(listing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available listings for a store
|
||||
/// </summary>
|
||||
/// <param name="user">The person getting the listings.</param>
|
||||
/// <param name="component">The store the listings are coming from.</param>
|
||||
/// <returns>The available listings.</returns>
|
||||
public IEnumerable<ListingData> GetAvailableListings(EntityUid user, StoreComponent component)
|
||||
{
|
||||
return GetAvailableListings(user, component.Listings, component.Categories, component.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available listings for a user given an overall set of listings and categories to filter by.
|
||||
/// </summary>
|
||||
/// <param name="user">The person getting the listings.</param>
|
||||
/// <param name="listings">All of the listings that are available. If null, will just get all listings from the prototypes.</param>
|
||||
/// <param name="categories">What categories to filter by.</param>
|
||||
/// <param name="storeEntity">The physial entity of the store. Can be null.</param>
|
||||
/// <returns>The available listings.</returns>
|
||||
public IEnumerable<ListingData> GetAvailableListings(EntityUid user, HashSet<ListingData>? listings, HashSet<string> categories, EntityUid? storeEntity = null)
|
||||
{
|
||||
if (listings == null)
|
||||
listings = GetAllListings();
|
||||
|
||||
foreach (var listing in listings)
|
||||
{
|
||||
if (!ListingHasCategory(listing, categories))
|
||||
continue;
|
||||
|
||||
if (listing.Conditions != null)
|
||||
{
|
||||
var args = new ListingConditionArgs(user, storeEntity, listing, EntityManager);
|
||||
var conditionsMet = true;
|
||||
|
||||
foreach (var condition in listing.Conditions)
|
||||
{
|
||||
if (!condition.Condition(args))
|
||||
{
|
||||
conditionsMet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!conditionsMet)
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return listing;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a listing appears in a list of given categories
|
||||
/// </summary>
|
||||
/// <param name="listing">The listing itself.</param>
|
||||
/// <param name="categories">The categories to check through.</param>
|
||||
/// <returns>If the listing was present in one of the categories.</returns>
|
||||
public bool ListingHasCategory(ListingData listing, HashSet<string> categories)
|
||||
{
|
||||
foreach (var cat in categories)
|
||||
{
|
||||
if (listing.Categories.Contains(cat))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
224
Content.Server/Store/Systems/StoreSystem.Ui.cs
Normal file
224
Content.Server/Store/Systems/StoreSystem.Ui.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Actions.ActionTypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Server.GameObjects;
|
||||
using System.Linq;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
public sealed partial class StoreSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _admin = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
|
||||
private void InitializeUi()
|
||||
{
|
||||
SubscribeLocalEvent<StoreComponent, StoreRequestUpdateInterfaceMessage>((_,c,r) => UpdateUserInterface(r.CurrentBuyer, c));
|
||||
SubscribeLocalEvent<StoreComponent, StoreBuyListingMessage>(OnBuyRequest);
|
||||
SubscribeLocalEvent<StoreComponent, StoreRequestWithdrawMessage>(OnRequestWithdraw);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the store Ui open and closed
|
||||
/// </summary>
|
||||
/// <param name="user">the person doing the toggling</param>
|
||||
/// <param name="component">the store being toggled</param>
|
||||
public void ToggleUi(EntityUid user, StoreComponent component)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(user, out var actor))
|
||||
return;
|
||||
|
||||
var ui = component.Owner.GetUIOrNull(StoreUiKey.Key);
|
||||
ui?.Toggle(actor.PlayerSession);
|
||||
|
||||
UpdateUserInterface(user, component, ui);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the user interface for a store and refreshes the listings
|
||||
/// </summary>
|
||||
/// <param name="user">The person who if opening the store ui. Listings are filtered based on this.</param>
|
||||
/// <param name="component">The store component being refreshed.</param>
|
||||
/// <param name="ui"></param>
|
||||
public void UpdateUserInterface(EntityUid? user, StoreComponent component, BoundUserInterface? ui = null)
|
||||
{
|
||||
if (ui == null)
|
||||
{
|
||||
ui = component.Owner.GetUIOrNull(StoreUiKey.Key);
|
||||
if (ui == null)
|
||||
return;
|
||||
}
|
||||
|
||||
//if we haven't opened it before, initialize the shit
|
||||
if (!component.Opened)
|
||||
{
|
||||
RefreshAllListings(component);
|
||||
InitializeFromPreset(component.Preset, component);
|
||||
component.Opened = true;
|
||||
}
|
||||
|
||||
//this is the person who will be passed into logic for all listing filtering.
|
||||
var buyer = user;
|
||||
if (buyer != null) //if we have no "buyer" for this update, then don't update the listings
|
||||
{
|
||||
if (component.AccountOwner != null) //if we have one stored, then use that instead
|
||||
buyer = component.AccountOwner.Value;
|
||||
|
||||
component.LastAvailableListings = GetAvailableListings(buyer.Value, component).ToHashSet();
|
||||
}
|
||||
|
||||
//dictionary for all currencies, including 0 values for currencies on the whitelist
|
||||
Dictionary<string, FixedPoint2> allCurrency = new();
|
||||
foreach (var supported in component.CurrencyWhitelist)
|
||||
{
|
||||
allCurrency.Add(supported, FixedPoint2.Zero);
|
||||
|
||||
if (component.Balance.ContainsKey(supported))
|
||||
allCurrency[supported] = component.Balance[supported];
|
||||
}
|
||||
|
||||
var state = new StoreUpdateState(buyer, component.LastAvailableListings, allCurrency);
|
||||
ui.SetState(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles whenever a purchase was made.
|
||||
/// </summary>
|
||||
private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListingMessage msg)
|
||||
{
|
||||
ListingData? listing = component.Listings.FirstOrDefault(x => x.Equals(msg.Listing));
|
||||
if (listing == null) //make sure this listing actually exists
|
||||
{
|
||||
Logger.Debug("listing does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
//verify that we can actually buy this listing and it wasn't added
|
||||
if (!ListingHasCategory(listing, component.Categories))
|
||||
return;
|
||||
//condition checking because why not
|
||||
if (listing.Conditions != null)
|
||||
{
|
||||
var args = new ListingConditionArgs(msg.Buyer, component.Owner, listing, EntityManager);
|
||||
var conditionsMet = true;
|
||||
|
||||
foreach (var condition in listing.Conditions.Where(condition => !condition.Condition(args)))
|
||||
conditionsMet = false;
|
||||
|
||||
if (!conditionsMet)
|
||||
return;
|
||||
}
|
||||
|
||||
//check that we have enough money
|
||||
foreach (var currency in listing.Cost)
|
||||
{
|
||||
if (!component.Balance.TryGetValue(currency.Key, out var balance) || balance < currency.Value)
|
||||
{
|
||||
_audio.Play(component.InsufficientFundsSound, Filter.SinglePlayer(msg.Session), uid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//subtract the cash
|
||||
foreach (var currency in listing.Cost)
|
||||
component.Balance[currency.Key] -= currency.Value;
|
||||
|
||||
//spawn entity
|
||||
if (listing.ProductEntity != null)
|
||||
{
|
||||
var product = Spawn(listing.ProductEntity, Transform(msg.Buyer).Coordinates);
|
||||
_hands.TryPickupAnyHand(msg.Buyer, product);
|
||||
}
|
||||
|
||||
//give action
|
||||
if (listing.ProductAction != null)
|
||||
{
|
||||
var action = new InstantAction(_proto.Index<InstantActionPrototype>(listing.ProductAction));
|
||||
_actions.AddAction(msg.Buyer, action, null);
|
||||
}
|
||||
|
||||
//broadcast event
|
||||
if (listing.ProductEvent != null)
|
||||
{
|
||||
RaiseLocalEvent(listing.ProductEvent);
|
||||
}
|
||||
|
||||
//log dat shit.
|
||||
if (TryComp<MindComponent>(msg.Buyer, out var mind))
|
||||
{
|
||||
_admin.Add(LogType.StorePurchase, LogImpact.Low,
|
||||
$"{ToPrettyString(mind.Owner):player} purchased listing \"{listing.Name}\" from {ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
listing.PurchaseAmount++; //track how many times something has been purchased
|
||||
_audio.Play(component.BuySuccessSound, Filter.SinglePlayer(msg.Session), uid); //cha-ching!
|
||||
|
||||
UpdateUserInterface(msg.Buyer, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles dispensing the currency you requested to be withdrawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This would need to be done should a currency with decimal values need to use it.
|
||||
/// not quite sure how to handle that
|
||||
/// </remarks>
|
||||
private void OnRequestWithdraw(EntityUid uid, StoreComponent component, StoreRequestWithdrawMessage msg)
|
||||
{
|
||||
//make sure we have enough cash in the bank and we actually support this currency
|
||||
if (!component.Balance.TryGetValue(msg.Currency, out var currentAmount) || currentAmount < msg.Amount)
|
||||
return;
|
||||
|
||||
//make sure a malicious client didn't send us random shit
|
||||
if (!_proto.TryIndex<CurrencyPrototype>(msg.Currency, out var proto))
|
||||
return;
|
||||
|
||||
//we need an actually valid entity to spawn. This check has been done earlier, but just in case.
|
||||
if (proto.EntityId == null || !proto.CanWithdraw)
|
||||
return;
|
||||
|
||||
var entproto = _proto.Index<EntityPrototype>(proto.EntityId);
|
||||
|
||||
var amountRemaining = msg.Amount;
|
||||
var coordinates = Transform(msg.Buyer).Coordinates;
|
||||
if (entproto.HasComponent<StackComponent>())
|
||||
{
|
||||
while (amountRemaining > 0)
|
||||
{
|
||||
var ent = Spawn(proto.EntityId, coordinates);
|
||||
var stackComponent = Comp<StackComponent>(ent); //we already know it exists
|
||||
|
||||
var amountPerStack = Math.Min(stackComponent.MaxCount, amountRemaining);
|
||||
|
||||
_stack.SetCount(ent, amountPerStack, stackComponent);
|
||||
amountRemaining -= amountPerStack;
|
||||
_hands.TryPickupAnyHand(msg.Buyer, ent);
|
||||
}
|
||||
}
|
||||
else //please for the love of christ give your currency stack component
|
||||
{
|
||||
while (amountRemaining > 0)
|
||||
{
|
||||
var ent = Spawn(proto.EntityId, coordinates);
|
||||
_hands.TryPickupAnyHand(msg.Buyer, ent);
|
||||
amountRemaining--;
|
||||
}
|
||||
}
|
||||
|
||||
component.Balance[msg.Currency] -= msg.Amount;
|
||||
UpdateUserInterface(msg.Buyer, component);
|
||||
}
|
||||
}
|
||||
153
Content.Server/Store/Systems/StoreSystem.cs
Normal file
153
Content.Server/Store/Systems/StoreSystem.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Content.Server.Stack;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Server.UserInterface;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Manages general interactions with a store and different entities,
|
||||
/// getting listings for stores, and interfacing with the store UI.
|
||||
/// </summary>
|
||||
public sealed partial class StoreSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CurrencyComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<StoreComponent, BeforeActivatableUIOpenEvent>((_,c,a) => UpdateUserInterface(a.User, c));
|
||||
|
||||
SubscribeLocalEvent<StoreComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<StoreComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
InitializeUi();
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, StoreComponent component, ComponentStartup args)
|
||||
{
|
||||
RaiseLocalEvent(uid, new StoreAddedEvent(), true);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, StoreComponent component, ComponentShutdown args)
|
||||
{
|
||||
RaiseLocalEvent(uid, new StoreRemovedEvent(), true);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, CurrencyComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach)
|
||||
return;
|
||||
|
||||
if (args.Target == null || !TryComp<StoreComponent>(args.Target, out var store))
|
||||
return;
|
||||
|
||||
//if you somehow are inserting cash before the store initializes.
|
||||
if (!store.Opened)
|
||||
{
|
||||
InitializeFromPreset(store.Preset, store);
|
||||
store.Opened = true;
|
||||
}
|
||||
|
||||
args.Handled = TryAddCurrency(GetCurrencyValue(component), store);
|
||||
|
||||
if (args.Handled)
|
||||
{
|
||||
var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target));
|
||||
_popup.PopupEntity(msg, args.Target.Value, Filter.Pvs(args.Target.Value));
|
||||
QueueDel(args.Used);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from an entity's currency component.
|
||||
/// Scales with stacks.
|
||||
/// </summary>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>The value of the currency</returns>
|
||||
public Dictionary<string, FixedPoint2> GetCurrencyValue(CurrencyComponent component)
|
||||
{
|
||||
TryComp<StackComponent>(component.Owner, out var stack);
|
||||
var amount = stack?.Count ?? 1;
|
||||
|
||||
return component.Price.ToDictionary(v => v.Key, p => p.Value * amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a currency to a store's balance.
|
||||
/// </summary>
|
||||
/// <param name="component">The currency to add</param>
|
||||
/// <param name="store">The store to add it to</param>
|
||||
/// <returns>Whether or not the currency was succesfully added</returns>
|
||||
public bool TryAddCurrency(CurrencyComponent component, StoreComponent store)
|
||||
{
|
||||
return TryAddCurrency(GetCurrencyValue(component), store);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a currency to a store's balance
|
||||
/// </summary>
|
||||
/// <param name="currency">The value to add to the store</param>
|
||||
/// <param name="store">The store to add it to</param>
|
||||
/// <returns>Whether or not the currency was succesfully added</returns>
|
||||
public bool TryAddCurrency(Dictionary<string, FixedPoint2> currency, StoreComponent store)
|
||||
{
|
||||
//verify these before values are modified
|
||||
foreach (var type in currency)
|
||||
{
|
||||
if (!store.CurrencyWhitelist.Contains(type.Key))
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var type in currency)
|
||||
{
|
||||
if (!store.Balance.TryAdd(type.Key, type.Value))
|
||||
store.Balance[type.Key] += type.Value;
|
||||
}
|
||||
|
||||
UpdateUserInterface(null, store);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a store based on a preset ID
|
||||
/// </summary>
|
||||
/// <param name="preset">The ID of a store preset prototype</param>
|
||||
/// <param name="component">The store being initialized</param>
|
||||
public void InitializeFromPreset(string? preset, StoreComponent component)
|
||||
{
|
||||
if (preset == null)
|
||||
return;
|
||||
|
||||
if (!_proto.TryIndex<StorePresetPrototype>(preset, out var proto))
|
||||
return;
|
||||
|
||||
InitializeFromPreset(proto, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a store based on a given preset
|
||||
/// </summary>
|
||||
/// <param name="preset">The StorePresetPrototype</param>
|
||||
/// <param name="component">The store being initialized</param>
|
||||
public void InitializeFromPreset(StorePresetPrototype preset, StoreComponent component)
|
||||
{
|
||||
component.Preset = preset.ID;
|
||||
component.CurrencyWhitelist.UnionWith(preset.CurrencyWhitelist);
|
||||
component.Categories.UnionWith(preset.Categories);
|
||||
if (component.Balance == new Dictionary<string, FixedPoint2>() && preset.InitialBalance != null) //if we don't have a value stored, use the preset
|
||||
TryAddCurrency(preset.InitialBalance, component);
|
||||
|
||||
var ui = component.Owner.GetUIOrNull(StoreUiKey.Key);
|
||||
ui?.SetState(new StoreInitializeState(preset.StoreName));
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,9 @@
|
||||
using Content.Shared.Tools;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Tools.Components
|
||||
namespace Content.Server.Tools.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMultipleToolComponent))]
|
||||
public sealed class MultipleToolComponent : SharedMultipleToolComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Not to be confused with Multitool (power)
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class MultipleToolComponent : SharedMultipleToolComponent
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed class ToolEntry
|
||||
{
|
||||
[DataField("behavior", required:true)]
|
||||
public PrototypeFlags<ToolQualityPrototype> Behavior { get; } = new();
|
||||
|
||||
[DataField("useSound")]
|
||||
public SoundSpecifier? Sound { get; } = null;
|
||||
|
||||
[DataField("changeSound")]
|
||||
public SoundSpecifier? ChangeSound { get; } = null;
|
||||
|
||||
[DataField("sprite")]
|
||||
public SpriteSpecifier? Sprite { get; } = null;
|
||||
}
|
||||
|
||||
[DataField("entries", required:true)]
|
||||
public ToolEntry[] Entries { get; } = Array.Empty<ToolEntry>();
|
||||
|
||||
[ViewVariables]
|
||||
public int CurrentEntry = 0;
|
||||
|
||||
[ViewVariables]
|
||||
public string CurrentQualityName = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Tools.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tools;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Tools
|
||||
{
|
||||
public sealed partial class ToolSystem
|
||||
{
|
||||
private void InitializeMultipleTools()
|
||||
{
|
||||
SubscribeLocalEvent<MultipleToolComponent, ComponentStartup>(OnMultipleToolStartup);
|
||||
SubscribeLocalEvent<MultipleToolComponent, ActivateInWorldEvent>(OnMultipleToolActivated);
|
||||
SubscribeLocalEvent<MultipleToolComponent, ComponentGetState>(OnMultipleToolGetState);
|
||||
}
|
||||
|
||||
private void OnMultipleToolStartup(EntityUid uid, MultipleToolComponent multiple, ComponentStartup args)
|
||||
{
|
||||
// Only set the multiple tool if we have a tool component.
|
||||
if(EntityManager.TryGetComponent(uid, out ToolComponent? tool))
|
||||
SetMultipleTool(uid, multiple, tool);
|
||||
}
|
||||
|
||||
private void OnMultipleToolActivated(EntityUid uid, MultipleToolComponent multiple, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = CycleMultipleTool(uid, multiple);
|
||||
}
|
||||
|
||||
private void OnMultipleToolGetState(EntityUid uid, MultipleToolComponent multiple, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MultipleToolComponentState(multiple.CurrentQualityName);
|
||||
}
|
||||
|
||||
public bool CycleMultipleTool(EntityUid uid, MultipleToolComponent? multiple = null)
|
||||
{
|
||||
if (!Resolve(uid, ref multiple))
|
||||
return false;
|
||||
|
||||
if (multiple.Entries.Length == 0)
|
||||
return false;
|
||||
|
||||
multiple.CurrentEntry = (multiple.CurrentEntry + 1) % multiple.Entries.Length;
|
||||
SetMultipleTool(uid, multiple);
|
||||
|
||||
var current = multiple.Entries[multiple.CurrentEntry];
|
||||
|
||||
if(current.ChangeSound is {} changeSound)
|
||||
SoundSystem.Play(changeSound.GetSound(), Filter.Pvs(uid), uid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetMultipleTool(EntityUid uid, MultipleToolComponent? multiple = null, ToolComponent? tool = null, SpriteComponent? sprite = null)
|
||||
{
|
||||
if (!Resolve(uid, ref multiple, ref tool))
|
||||
return;
|
||||
|
||||
// Sprite is optional.
|
||||
Resolve(uid, ref sprite, false);
|
||||
|
||||
if (multiple.Entries.Length == 0)
|
||||
{
|
||||
multiple.CurrentQualityName = Loc.GetString("multiple-tool-component-no-behavior");
|
||||
multiple.Dirty();
|
||||
return;
|
||||
}
|
||||
|
||||
var current = multiple.Entries[multiple.CurrentEntry];
|
||||
|
||||
tool.UseSound = current.Sound;
|
||||
tool.Qualities = current.Behavior;
|
||||
|
||||
if (_prototypeManager.TryIndex(current.Behavior.First(), out ToolQualityPrototype? quality))
|
||||
{
|
||||
multiple.CurrentQualityName = Loc.GetString(quality.Name);
|
||||
}
|
||||
|
||||
multiple.Dirty();
|
||||
|
||||
if (current.Sprite == null || sprite == null)
|
||||
return;
|
||||
|
||||
sprite.LayerSetSprite(0, current.Sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Temperature;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -228,11 +227,15 @@ namespace Content.Server.Tools
|
||||
var drained = _solutionContainerSystem.Drain(target, targetSolution, trans);
|
||||
_solutionContainerSystem.TryAddSolution(uid, welderSolution, drained);
|
||||
SoundSystem.Play(welder.WelderRefill.GetSound(), Filter.Pvs(uid), uid);
|
||||
target.PopupMessage(args.User, Loc.GetString("welder-component-after-interact-refueled-message"));
|
||||
_popupSystem.PopupEntity(Loc.GetString("welder-component-after-interact-refueled-message"), uid, Filter.Entities(args.User));
|
||||
}
|
||||
else if (welderSolution.AvailableVolume <= 0)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("welder-component-already-full"), uid, Filter.Entities(args.User));
|
||||
}
|
||||
else
|
||||
{
|
||||
target.PopupMessage(args.User, Loc.GetString("welder-component-no-fuel-in-tank", ("owner", args.Target)));
|
||||
_popupSystem.PopupEntity(Loc.GetString("welder-component-no-fuel-in-tank", ("owner", args.Target)), uid, Filter.Entities(args.User));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Tools;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Content.Server.Tools
|
||||
{
|
||||
// TODO move tool system to shared, and make it a friend of Tool Component.
|
||||
public sealed partial class ToolSystem : EntitySystem
|
||||
public sealed partial class ToolSystem : SharedToolSystem
|
||||
{
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
@@ -34,7 +33,6 @@ namespace Content.Server.Tools
|
||||
|
||||
InitializeTilePrying();
|
||||
InitializeWelders();
|
||||
InitializeMultipleTools();
|
||||
|
||||
SubscribeLocalEvent<ToolDoAfterComplete>(OnDoAfterComplete);
|
||||
SubscribeLocalEvent<ToolDoAfterCancelled>(OnDoAfterCancelled);
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink.Account
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes when one of the UplinkAccounts changed its TC balance
|
||||
/// </summary>
|
||||
public sealed class UplinkAccountBalanceChanged : EntityEventArgs
|
||||
{
|
||||
public readonly UplinkAccount Account;
|
||||
|
||||
/// <summary>
|
||||
/// Difference between NewBalance - OldBalance
|
||||
/// </summary>
|
||||
public readonly int Difference;
|
||||
|
||||
public readonly int NewBalance;
|
||||
public readonly int OldBalance;
|
||||
|
||||
public UplinkAccountBalanceChanged(UplinkAccount account, int difference)
|
||||
{
|
||||
Account = account;
|
||||
Difference = difference;
|
||||
|
||||
NewBalance = account.Balance;
|
||||
OldBalance = account.Balance - difference;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink.Account
|
||||
{
|
||||
/// <summary>
|
||||
/// Manage all registred uplink accounts and their balance
|
||||
/// </summary>
|
||||
public sealed class UplinkAccountsSystem : EntitySystem
|
||||
{
|
||||
public const string TelecrystalProtoId = "Telecrystal";
|
||||
|
||||
[Dependency]
|
||||
private readonly UplinkListingSytem _listingSystem = default!;
|
||||
[Dependency]
|
||||
private readonly SharedStackSystem _stackSystem = default!;
|
||||
|
||||
private readonly HashSet<UplinkAccount> _accounts = new();
|
||||
|
||||
public bool AddNewAccount(UplinkAccount acc)
|
||||
{
|
||||
return _accounts.Add(acc);
|
||||
}
|
||||
|
||||
public bool HasAccount(EntityUid holder) =>
|
||||
_accounts.Any(acct => acct.AccountHolder == holder);
|
||||
|
||||
/// <summary>
|
||||
/// Add TC to uplinks account balance
|
||||
/// </summary>
|
||||
public bool AddToBalance(UplinkAccount account, int toAdd)
|
||||
{
|
||||
account.Balance += toAdd;
|
||||
|
||||
RaiseLocalEvent(new UplinkAccountBalanceChanged(account, toAdd));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Charge TC from uplinks account balance
|
||||
/// </summary>
|
||||
public bool RemoveFromBalance(UplinkAccount account, int price)
|
||||
{
|
||||
if (account.Balance - price < 0)
|
||||
return false;
|
||||
|
||||
account.Balance -= price;
|
||||
|
||||
RaiseLocalEvent(new UplinkAccountBalanceChanged(account, -price));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force-set TC uplinks account balance to a new value
|
||||
/// </summary>
|
||||
public bool SetBalance(UplinkAccount account, int newBalance)
|
||||
{
|
||||
if (newBalance < 0)
|
||||
return false;
|
||||
|
||||
var dif = newBalance - account.Balance;
|
||||
account.Balance = newBalance;
|
||||
RaiseLocalEvent(new UplinkAccountBalanceChanged(account, dif));
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public bool TryPurchaseItem(UplinkAccount acc, string itemId, EntityCoordinates spawnCoords, [NotNullWhen(true)] out EntityUid? purchasedItem)
|
||||
{
|
||||
purchasedItem = null;
|
||||
|
||||
if (!_listingSystem.TryGetListing(itemId, out var listing))
|
||||
return false;
|
||||
|
||||
if (acc.Balance < listing.Price)
|
||||
return false;
|
||||
|
||||
if (!RemoveFromBalance(acc, listing.Price))
|
||||
return false;
|
||||
|
||||
purchasedItem = EntityManager.SpawnEntity(listing.ItemId, spawnCoords);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryWithdrawTC(UplinkAccount acc, int tc, EntityCoordinates spawnCoords, [NotNullWhen(true)] out EntityUid? stackUid)
|
||||
{
|
||||
stackUid = null;
|
||||
|
||||
// try to charge TC from players account
|
||||
var actTC = Math.Min(tc, acc.Balance);
|
||||
if (actTC <= 0)
|
||||
return false;
|
||||
if (!RemoveFromBalance(acc, actTC))
|
||||
return false;
|
||||
|
||||
// create a stack of TCs near player
|
||||
var stackEntity = EntityManager.SpawnEntity(TelecrystalProtoId, spawnCoords);
|
||||
stackUid = stackEntity;
|
||||
|
||||
// set right amount in stack
|
||||
_stackSystem.SetCount(stackUid.Value, actTC);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
@@ -82,15 +81,10 @@ namespace Content.Server.Traitor.Uplink.Commands
|
||||
// Get TC count
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
var tcCount = configManager.GetCVar(CCVars.TraitorStartingBalance);
|
||||
|
||||
// Get account
|
||||
var uplinkAccount = new UplinkAccount(tcCount, user);
|
||||
var accounts = entityManager.EntitySysManager.GetEntitySystem<UplinkAccountsSystem>();
|
||||
accounts.AddNewAccount(uplinkAccount);
|
||||
|
||||
Logger.Debug(entityManager.ToPrettyString(user));
|
||||
// Finally add uplink
|
||||
if (!entityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
|
||||
.AddUplink(user, uplinkAccount, uplinkEntity))
|
||||
var uplinkSys = entityManager.EntitySysManager.GetEntitySystem<UplinkSystem>();
|
||||
if (!uplinkSys.AddUplink(user, FixedPoint2.New(tcCount), uplinkEntity: uplinkEntity))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("add-uplink-command-error-2"));
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using Content.Shared.Store;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink.SurplusBundle;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,4 +15,11 @@ public sealed class SurplusBundleComponent : Component
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[DataField("totalPrice")]
|
||||
public int TotalPrice = 20;
|
||||
|
||||
/// <summary>
|
||||
/// The preset that will be used to get all the listings.
|
||||
/// Currently just defaults to the basic uplink.
|
||||
/// </summary>
|
||||
[DataField("storePreset", customTypeSerializer: typeof(PrototypeIdSerializer<StorePresetPrototype>))]
|
||||
public string StorePreset = "StorePresetUplink";
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -12,23 +13,25 @@ public sealed class SurplusBundleSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
|
||||
private UplinkStoreListingPrototype[] _uplinks = default!;
|
||||
private ListingData[] _listings = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SurplusBundleComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
InitList();
|
||||
SubscribeLocalEvent<SurplusBundleComponent, ComponentInit>(OnInit);
|
||||
}
|
||||
|
||||
private void InitList()
|
||||
private void OnInit(EntityUid uid, SurplusBundleComponent component, ComponentInit args)
|
||||
{
|
||||
// sort data in price descending order
|
||||
_uplinks = _prototypeManager.EnumeratePrototypes<UplinkStoreListingPrototype>()
|
||||
.Where(item => item.CanSurplus).ToArray();
|
||||
Array.Sort(_uplinks, (a, b) => b.Price - a.Price);
|
||||
var storePreset = _prototypeManager.Index<StorePresetPrototype>(component.StorePreset);
|
||||
|
||||
_listings = _store.GetAvailableListings(uid, null, storePreset.Categories).ToArray();
|
||||
|
||||
Array.Sort(_listings, (a, b) => (int) (b.Cost.Values.Sum() - a.Cost.Values.Sum())); //this might get weird with multicurrency but don't think about it
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SurplusBundleComponent component, MapInitEvent args)
|
||||
@@ -46,19 +49,19 @@ public sealed class SurplusBundleSystem : EntitySystem
|
||||
var content = GetRandomContent(component.TotalPrice);
|
||||
foreach (var item in content)
|
||||
{
|
||||
var ent = EntityManager.SpawnEntity(item.ItemId, cords);
|
||||
var ent = EntityManager.SpawnEntity(item.ProductEntity, cords);
|
||||
_entityStorage.Insert(ent, component.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
// wow, is this leetcode reference?
|
||||
private List<UplinkStoreListingPrototype> GetRandomContent(int targetCost)
|
||||
private List<ListingData> GetRandomContent(FixedPoint2 targetCost)
|
||||
{
|
||||
var ret = new List<UplinkStoreListingPrototype>();
|
||||
if (_uplinks.Length == 0)
|
||||
var ret = new List<ListingData>();
|
||||
if (_listings.Length == 0)
|
||||
return ret;
|
||||
|
||||
var totalCost = 0;
|
||||
var totalCost = FixedPoint2.Zero;
|
||||
var index = 0;
|
||||
while (totalCost < targetCost)
|
||||
{
|
||||
@@ -66,10 +69,10 @@ public sealed class SurplusBundleSystem : EntitySystem
|
||||
// Find new item with the lowest acceptable price
|
||||
// All expansive items will be before index, all acceptable after
|
||||
var remainingBudget = targetCost - totalCost;
|
||||
while (_uplinks[index].Price > remainingBudget)
|
||||
while (_listings[index].Cost.Values.Sum() > remainingBudget)
|
||||
{
|
||||
index++;
|
||||
if (index >= _uplinks.Length)
|
||||
if (index >= _listings.Length)
|
||||
{
|
||||
// Looks like no cheap items left
|
||||
// It shouldn't be case for ss14 content
|
||||
@@ -79,10 +82,10 @@ public sealed class SurplusBundleSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Select random listing and add into crate
|
||||
var randomIndex = _random.Next(index, _uplinks.Length);
|
||||
var randomItem = _uplinks[randomIndex];
|
||||
var randomIndex = _random.Next(index, _listings.Length);
|
||||
var randomItem = _listings[randomIndex];
|
||||
ret.Add(randomItem);
|
||||
totalCost += randomItem.Price;
|
||||
totalCost += randomItem.Cost.Values.Sum();
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Content.Server.Traitor.Uplink.Telecrystal
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class TelecrystalComponent : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Server.Traitor.Uplink.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink.Telecrystal
|
||||
{
|
||||
public sealed class TelecrystalSystem : EntitySystem
|
||||
{
|
||||
[Dependency]
|
||||
private readonly UplinkAccountsSystem _accounts = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<TelecrystalComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, TelecrystalComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach)
|
||||
return;
|
||||
|
||||
if (args.Target == null || !EntityManager.TryGetComponent(args.Target.Value, out UplinkComponent? uplink))
|
||||
return;
|
||||
|
||||
// TODO: when uplink will have some auth logic (like PDA ringtone code)
|
||||
// check if uplink open before adding TC
|
||||
// No metagaming by using this on every PDA around just to see if it gets used up.
|
||||
|
||||
var acc = uplink.UplinkAccount;
|
||||
if (acc == null)
|
||||
return;
|
||||
|
||||
EntityManager.TryGetComponent(uid, out SharedStackComponent? stack);
|
||||
|
||||
var tcCount = stack != null ? stack.Count : 1;
|
||||
if (!_accounts.AddToBalance(acc, tcCount))
|
||||
return;
|
||||
|
||||
var msg = Loc.GetString("telecrystal-component-sucs-inserted",
|
||||
("source", args.Used), ("target", args.Target));
|
||||
|
||||
args.User.PopupMessage(args.User, msg);
|
||||
|
||||
EntityManager.DeleteEntity(uid);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class UplinkComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
[DataField("buySuccessSound")]
|
||||
public SoundSpecifier BuySuccessSound = new SoundPathSpecifier("/Audio/Effects/kaching.ogg");
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("insufficientFundsSound")]
|
||||
public SoundSpecifier InsufficientFundsSound = new SoundPathSpecifier("/Audio/Effects/error.ogg");
|
||||
|
||||
[DataField("activatesInHands")]
|
||||
public bool ActivatesInHands = false;
|
||||
|
||||
[DataField("presetInfo")]
|
||||
public PresetUplinkInfo? PresetInfo = null;
|
||||
|
||||
[ViewVariables] public UplinkAccount? UplinkAccount;
|
||||
|
||||
[ViewVariables, DataField("jobWhiteList", customTypeSerializer:typeof(PrototypeIdHashSetSerializer<JobPrototype>))]
|
||||
public HashSet<string>? JobWhitelist = null;
|
||||
|
||||
[Serializable]
|
||||
[DataDefinition]
|
||||
public sealed class PresetUplinkInfo
|
||||
{
|
||||
[DataField("balance")]
|
||||
public int StartingBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Server.Traitor.Uplink.Components;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink
|
||||
{
|
||||
public sealed class UplinkInitEvent : EntityEventArgs
|
||||
{
|
||||
public UplinkComponent Uplink;
|
||||
|
||||
public UplinkInitEvent(UplinkComponent uplink)
|
||||
{
|
||||
Uplink = uplink;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UplinkRemovedEvent : EntityEventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains and controls all items in traitors uplink shop
|
||||
/// </summary>
|
||||
public sealed class UplinkListingSytem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly Dictionary<string, UplinkListingData> _listings = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var item in _prototypeManager.EnumeratePrototypes<UplinkStoreListingPrototype>())
|
||||
{
|
||||
var newListing = new UplinkListingData(item.ListingName, item.ItemId,
|
||||
item.Price, item.Category, item.Description, item.Icon, item.JobWhitelist);
|
||||
|
||||
RegisterUplinkListing(newListing);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterUplinkListing(UplinkListingData listing)
|
||||
{
|
||||
if (!ContainsListing(listing))
|
||||
{
|
||||
_listings.Add(listing.ItemId, listing);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsListing(UplinkListingData listing)
|
||||
{
|
||||
return _listings.ContainsKey(listing.ItemId);
|
||||
}
|
||||
|
||||
public bool TryGetListing(string itemID, [NotNullWhen(true)] out UplinkListingData? data)
|
||||
{
|
||||
return _listings.TryGetValue(itemID, out data);
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, UplinkListingData> GetListings()
|
||||
{
|
||||
return _listings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +1,41 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Server.Traitor.Uplink.Components;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
namespace Content.Server.Traitor.Uplink
|
||||
{
|
||||
public sealed class UplinkSystem : EntitySystem
|
||||
{
|
||||
[Dependency]
|
||||
private readonly UplinkAccountsSystem _accounts = default!;
|
||||
[Dependency]
|
||||
private readonly UplinkListingSytem _listing = default!;
|
||||
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
|
||||
public override void Initialize()
|
||||
public const string TelecrystalCurrencyPrototype = "Telecrystal";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of TC on an "uplink"
|
||||
/// Mostly just here for legacy systems based on uplink.
|
||||
/// </summary>
|
||||
/// <param name="component"></param>
|
||||
/// <returns>the amount of TC</returns>
|
||||
public int GetTCBalance(StoreComponent component)
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<UplinkComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<UplinkComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<UplinkComponent, ActivateInWorldEvent>(OnActivate);
|
||||
|
||||
// UI events
|
||||
SubscribeLocalEvent<UplinkComponent, UplinkBuyListingMessage>(OnBuy);
|
||||
SubscribeLocalEvent<UplinkComponent, UplinkRequestUpdateInterfaceMessage>(OnRequestUpdateUI);
|
||||
SubscribeLocalEvent<UplinkComponent, UplinkTryWithdrawTC>(OnWithdrawTC);
|
||||
|
||||
SubscribeLocalEvent<UplinkAccountBalanceChanged>(OnBalanceChangedBroadcast);
|
||||
FixedPoint2? tcBalance = component.Balance.GetValueOrDefault(TelecrystalCurrencyPrototype);
|
||||
return tcBalance != null ? tcBalance.Value.Int() : 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, UplinkComponent component, ComponentInit args)
|
||||
{
|
||||
RaiseLocalEvent(uid, new UplinkInitEvent(component), true);
|
||||
|
||||
// if component has a preset info (probably spawn by admin)
|
||||
// create a new account and register it for this uplink
|
||||
if (component.PresetInfo != null)
|
||||
{
|
||||
var account = new UplinkAccount(component.PresetInfo.StartingBalance);
|
||||
_accounts.AddNewAccount(account);
|
||||
SetAccount(component, account);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, UplinkComponent component, ComponentRemove args)
|
||||
{
|
||||
RaiseLocalEvent(uid, new UplinkRemovedEvent(), true);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, UplinkComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// check if uplinks activates directly or use some proxy, like a PDA
|
||||
if (!component.ActivatesInHands)
|
||||
return;
|
||||
if (component.UplinkAccount == null)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
ToggleUplinkUI(component, actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBalanceChangedBroadcast(UplinkAccountBalanceChanged ev)
|
||||
{
|
||||
foreach (var uplink in EntityManager.EntityQuery<UplinkComponent>())
|
||||
{
|
||||
if (uplink.UplinkAccount == ev.Account)
|
||||
{
|
||||
UpdateUserInterface(uplink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestUpdateUI(EntityUid uid, UplinkComponent uplink, UplinkRequestUpdateInterfaceMessage args)
|
||||
{
|
||||
UpdateUserInterface(uplink);
|
||||
}
|
||||
|
||||
private void OnBuy(EntityUid uid, UplinkComponent uplink, UplinkBuyListingMessage message)
|
||||
{
|
||||
if (message.Session.AttachedEntity is not { Valid: true } player) return;
|
||||
if (uplink.UplinkAccount == null) return;
|
||||
|
||||
if (!_accounts.TryPurchaseItem(uplink.UplinkAccount, message.ItemId,
|
||||
EntityManager.GetComponent<TransformComponent>(player).Coordinates, out var entity))
|
||||
{
|
||||
SoundSystem.Play(uplink.InsufficientFundsSound.GetSound(),
|
||||
Filter.SinglePlayer(message.Session), uplink.Owner, AudioParams.Default);
|
||||
RaiseNetworkEvent(new UplinkInsufficientFundsMessage(), message.Session.ConnectedClient);
|
||||
return;
|
||||
}
|
||||
|
||||
_handsSystem.PickupOrDrop(player, entity.Value);
|
||||
|
||||
SoundSystem.Play(uplink.BuySuccessSound.GetSound(),
|
||||
Filter.SinglePlayer(message.Session), uplink.Owner, AudioParams.Default.WithVolume(-8f));
|
||||
|
||||
RaiseNetworkEvent(new UplinkBuySuccessMessage(), message.Session.ConnectedClient);
|
||||
}
|
||||
|
||||
private void OnWithdrawTC(EntityUid uid, UplinkComponent uplink, UplinkTryWithdrawTC args)
|
||||
{
|
||||
var acc = uplink.UplinkAccount;
|
||||
if (acc == null)
|
||||
return;
|
||||
|
||||
if (args.Session.AttachedEntity is not { Valid: true } player) return;
|
||||
var cords = EntityManager.GetComponent<TransformComponent>(player).Coordinates;
|
||||
|
||||
// try to withdraw TCs from account
|
||||
if (!_accounts.TryWithdrawTC(acc, args.TC, cords, out var tcUid))
|
||||
return;
|
||||
|
||||
// try to put it into players hands
|
||||
_handsSystem.PickupOrDrop(player, tcUid.Value);
|
||||
|
||||
// play buying sound
|
||||
SoundSystem.Play(uplink.BuySuccessSound.GetSound(),
|
||||
Filter.SinglePlayer(args.Session), uplink.Owner, AudioParams.Default.WithVolume(-8f));
|
||||
|
||||
UpdateUserInterface(uplink);
|
||||
}
|
||||
|
||||
public void ToggleUplinkUI(UplinkComponent component, IPlayerSession session)
|
||||
{
|
||||
var ui = component.Owner.GetUIOrNull(UplinkUiKey.Key);
|
||||
ui?.Toggle(session);
|
||||
|
||||
UpdateUserInterface(component);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(UplinkComponent component)
|
||||
{
|
||||
var ui = component.Owner.GetUIOrNull(UplinkUiKey.Key);
|
||||
if (ui == null)
|
||||
return;
|
||||
|
||||
var listings = _listing.GetListings().Values.ToList();
|
||||
var acc = component.UplinkAccount;
|
||||
|
||||
UplinkAccountData accData;
|
||||
if (acc != null)
|
||||
{
|
||||
// if we don't have a jobwhitelist stored, get a new one
|
||||
if (component.JobWhitelist == null &&
|
||||
acc.AccountHolder != null &&
|
||||
TryComp<MindComponent>(acc.AccountHolder, out var mind) &&
|
||||
mind.Mind != null)
|
||||
{
|
||||
HashSet<string>? jobList = new();
|
||||
foreach (var role in mind.Mind.AllRoles.ToList())
|
||||
{
|
||||
if (role.GetType() == typeof(Job))
|
||||
{
|
||||
var job = (Job) role;
|
||||
jobList.Add(job.Prototype.ID);
|
||||
}
|
||||
}
|
||||
component.JobWhitelist = jobList;
|
||||
}
|
||||
|
||||
// filter out items not on the whitelist
|
||||
for (var i = 0; i < listings.Count; i++)
|
||||
{
|
||||
var entry = listings[i];
|
||||
if (entry.JobWhitelist != null)
|
||||
{
|
||||
var found = false;
|
||||
if (component.JobWhitelist != null)
|
||||
{
|
||||
foreach (var job in component.JobWhitelist)
|
||||
{
|
||||
if (entry.JobWhitelist.Contains(job))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
listings.Remove(entry);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
accData = new UplinkAccountData(acc.AccountHolder, acc.Balance);
|
||||
}
|
||||
else
|
||||
{
|
||||
accData = new UplinkAccountData(null, 0);
|
||||
}
|
||||
|
||||
ui.SetState(new UplinkUpdateState(accData, listings.ToArray()));
|
||||
}
|
||||
|
||||
public bool AddUplink(EntityUid user, UplinkAccount account, EntityUid? uplinkEntity = null)
|
||||
/// <summary>
|
||||
/// Adds an uplink to the target
|
||||
/// </summary>
|
||||
/// <param name="user">The person who is getting the uplink</param>
|
||||
/// <param name="balance">The amount of currency on the uplink. If null, will just use the amount specified in the preset.</param>
|
||||
/// <param name="uplinkPresetId">The id of the storepreset</param>
|
||||
/// <param name="uplinkEntity">The entity that will actually have the uplink functionality. Defaults to the PDA if null.</param>
|
||||
/// <returns>Whether or not the uplink was added successfully</returns>
|
||||
public bool AddUplink(EntityUid user, FixedPoint2? balance, string uplinkPresetId = "StorePresetUplink", EntityUid? uplinkEntity = null)
|
||||
{
|
||||
// Try to find target item
|
||||
if (uplinkEntity == null)
|
||||
@@ -234,11 +45,17 @@ namespace Content.Server.Traitor.Uplink
|
||||
return false;
|
||||
}
|
||||
|
||||
var uplink = uplinkEntity.Value.EnsureComponent<UplinkComponent>();
|
||||
SetAccount(uplink, account);
|
||||
var store = EnsureComp<StoreComponent>(uplinkEntity.Value);
|
||||
_store.InitializeFromPreset(uplinkPresetId, store);
|
||||
store.AccountOwner = user;
|
||||
store.Balance.Clear();
|
||||
|
||||
if (!HasComp<PDAComponent>(uplinkEntity.Value))
|
||||
uplink.ActivatesInHands = true;
|
||||
if (balance != null)
|
||||
{
|
||||
store.Balance.Clear();
|
||||
_store.TryAddCurrency(
|
||||
new Dictionary<string, FixedPoint2>() { { TelecrystalCurrencyPrototype, balance.Value } }, store);
|
||||
}
|
||||
|
||||
// TODO add BUI. Currently can't be done outside of yaml -_-
|
||||
|
||||
@@ -248,14 +65,13 @@ namespace Content.Server.Traitor.Uplink
|
||||
private EntityUid? FindUplinkTarget(EntityUid user)
|
||||
{
|
||||
// Try to find PDA in inventory
|
||||
|
||||
if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator))
|
||||
{
|
||||
while (containerSlotEnumerator.MoveNext(out var pdaUid))
|
||||
{
|
||||
if (!pdaUid.ContainedEntity.HasValue) continue;
|
||||
|
||||
if (HasComp<PDAComponent>(pdaUid.ContainedEntity.Value))
|
||||
if (HasComp<PDAComponent>(pdaUid.ContainedEntity.Value) || HasComp<StoreComponent>(pdaUid.ContainedEntity.Value))
|
||||
return pdaUid.ContainedEntity.Value;
|
||||
}
|
||||
}
|
||||
@@ -263,7 +79,7 @@ namespace Content.Server.Traitor.Uplink
|
||||
// Also check hands
|
||||
foreach (var item in _handsSystem.EnumerateHeld(user))
|
||||
{
|
||||
if (HasComp<PDAComponent>(item))
|
||||
if (HasComp<PDAComponent>(item) || HasComp<StoreComponent>(item))
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Server.Traitor.Uplink.Components;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.TraitorDeathMatch.Components;
|
||||
using Content.Server.Store.Components;
|
||||
using Content.Server.Store.Systems;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
@@ -12,8 +14,11 @@ namespace Content.Server.TraitorDeathMatch;
|
||||
public sealed class TraitorDeathMatchRedemptionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly UplinkAccountsSystem _uplink = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly UplinkSystem _uplink = default!;
|
||||
[Dependency] private readonly StoreSystem _store = default!;
|
||||
|
||||
private const string TcCurrencyPrototype = "Telecrystal";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -43,7 +48,7 @@ public sealed class TraitorDeathMatchRedemptionSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.TryGetComponent<UplinkComponent>(args.Used, out var victimUplink))
|
||||
if (!EntityManager.TryGetComponent<StoreComponent>(args.Used, out var victimUplink))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(
|
||||
"traitor-death-match-redemption-component-interact-using-main-message",
|
||||
@@ -72,10 +77,10 @@ public sealed class TraitorDeathMatchRedemptionSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
UplinkComponent? userUplink = null;
|
||||
StoreComponent? userUplink = null;
|
||||
|
||||
if (_inventory.TryGetSlotEntity(args.User, "id", out var pdaUid) &&
|
||||
EntityManager.TryGetComponent<UplinkComponent>(pdaUid, out var userUplinkComponent))
|
||||
EntityManager.TryGetComponent<StoreComponent>(pdaUid, out var userUplinkComponent))
|
||||
userUplink = userUplinkComponent;
|
||||
|
||||
if (userUplink == null)
|
||||
@@ -88,35 +93,13 @@ public sealed class TraitorDeathMatchRedemptionSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// We have finally determined both PDA components. FINALLY.
|
||||
|
||||
var userAccount = userUplink.UplinkAccount;
|
||||
var victimAccount = victimUplink.UplinkAccount;
|
||||
|
||||
if (userAccount == null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(
|
||||
"traitor-death-match-redemption-component-interact-using-main-message",
|
||||
("secondMessage",
|
||||
Loc.GetString(
|
||||
"traitor-death-match-redemption-component-interact-using-user-no-uplink-account-message"))), uid, Filter.Entities(args.User));
|
||||
return;
|
||||
}
|
||||
|
||||
if (victimAccount == null)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString(
|
||||
"traitor-death-match-redemption-component-interact-using-main-message",
|
||||
("secondMessage",
|
||||
Loc.GetString(
|
||||
"traitor-death-match-redemption-component-interact-using-victim-no-uplink-account-message"))), uid, Filter.Entities(args.User));
|
||||
return;
|
||||
}
|
||||
|
||||
// 4 is the per-PDA bonus amount.
|
||||
var transferAmount = victimAccount.Balance + 4;
|
||||
_uplink.SetBalance(victimAccount, 0);
|
||||
_uplink.AddToBalance(userAccount, transferAmount);
|
||||
// 4 is the per-PDA bonus amount
|
||||
var transferAmount = _uplink.GetTCBalance(victimUplink) + 4;
|
||||
victimUplink.Balance.Clear();
|
||||
_store.TryAddCurrency(new Dictionary<string, FixedPoint2>() { {"Telecrystal", FixedPoint2.New(transferAmount)}}, userUplink);
|
||||
|
||||
EntityManager.DeleteEntity(victimUplink.Owner);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Content.Server.UserInterface
|
||||
{
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _blockerSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -56,11 +57,7 @@ namespace Content.Server.UserInterface
|
||||
if (!TryComp(args.Performer, out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
if (!component.TryGetBoundUserInterface(args.Key, out var bui))
|
||||
return;
|
||||
|
||||
bui.Toggle(actor.PlayerSession);
|
||||
args.Handled = true;
|
||||
args.Handled = _uiSystem.TryToggleUi(uid, args.Key, actor.PlayerSession);
|
||||
}
|
||||
|
||||
private void AddOpenUiVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
|
||||
@@ -4,9 +4,10 @@ namespace Content.Server.UserInterface
|
||||
{
|
||||
public static class UserInterfaceHelpers
|
||||
{
|
||||
public static BoundUserInterface? GetUIOrNull(this EntityUid entity, object uiKey)
|
||||
[Obsolete("Use UserInterfaceSystem")]
|
||||
public static BoundUserInterface? GetUIOrNull(this EntityUid entity, Enum uiKey)
|
||||
{
|
||||
return IoCManager.Resolve<IEntityManager>().GetComponentOrNull<ServerUserInterfaceComponent>(entity)?.GetBoundUserInterfaceOrNull(uiKey);
|
||||
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().GetUiOrNull(entity, uiKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace Content.Server.Weapon.Melee.EnergySword
|
||||
|
||||
public bool Activated = false;
|
||||
|
||||
[DataField("isSharp")]
|
||||
public bool IsSharp = true;
|
||||
|
||||
/// <summary>
|
||||
/// RGB cycle rate for hacked e-swords.
|
||||
/// </summary>
|
||||
@@ -40,10 +43,10 @@ namespace Content.Server.Weapon.Melee.EnergySword
|
||||
Color.MediumOrchid
|
||||
};
|
||||
|
||||
[DataField("litDamageBonus", required: true)]
|
||||
public DamageSpecifier LitDamageBonus = default!;
|
||||
[DataField("litDamageBonus")]
|
||||
public DamageSpecifier LitDamageBonus = new();
|
||||
|
||||
[DataField("litDisarmMalus", required: true)]
|
||||
[DataField("litDisarmMalus")]
|
||||
public float litDisarmMalus = 0.6f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ namespace Content.Server.Weapon.Melee.EnergySword
|
||||
if(TryComp<MeleeWeaponComponent>(comp.Owner, out var weaponComp))
|
||||
weaponComp.HitSound = comp.OnHitOff;
|
||||
|
||||
RemComp<SharpComponent>(comp.Owner);
|
||||
if (comp.IsSharp)
|
||||
RemComp<SharpComponent>(comp.Owner);
|
||||
|
||||
SoundSystem.Play(comp.DeActivateSound.GetSound(), Filter.Pvs(comp.Owner, entityManager: EntityManager), comp.Owner);
|
||||
|
||||
@@ -97,7 +98,8 @@ namespace Content.Server.Weapon.Melee.EnergySword
|
||||
_item.SetSize(comp.Owner, 9999, item);
|
||||
}
|
||||
|
||||
EnsureComp<SharpComponent>(comp.Owner);
|
||||
if (comp.IsSharp)
|
||||
EnsureComp<SharpComponent>(comp.Owner);
|
||||
|
||||
if(TryComp<MeleeWeaponComponent>(comp.Owner, out var weaponComp))
|
||||
weaponComp.HitSound = comp.OnHitOn;
|
||||
|
||||
@@ -225,6 +225,9 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
public void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound)
|
||||
{
|
||||
if (Deleted(otherEntity))
|
||||
return;
|
||||
|
||||
// Like projectiles and melee,
|
||||
// 1. Entity specific sound
|
||||
// 2. Ammo's sound
|
||||
@@ -237,24 +240,20 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
if (type != null && rangedSound.SoundTypes?.TryGetValue(type, out var damageSoundType) == true)
|
||||
{
|
||||
SoundSystem.Play(damageSoundType!.GetSound(),
|
||||
Filter.Pvs(otherEntity, entityManager: EntityManager),
|
||||
otherEntity, AudioHelpers.WithVariation(DamagePitchVariation));
|
||||
|
||||
Audio.PlayPvs(damageSoundType, otherEntity, AudioParams.Default.WithVariation(DamagePitchVariation));
|
||||
playedSound = true;
|
||||
}
|
||||
else if (type != null && rangedSound.SoundGroups?.TryGetValue(type, out var damageSoundGroup) == true)
|
||||
{
|
||||
SoundSystem.Play(damageSoundGroup!.GetSound(),
|
||||
Filter.Pvs(otherEntity, entityManager: EntityManager),
|
||||
otherEntity, AudioHelpers.WithVariation(DamagePitchVariation));
|
||||
|
||||
Audio.PlayPvs(damageSoundGroup, otherEntity, AudioParams.Default.WithVariation(DamagePitchVariation));
|
||||
playedSound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!playedSound && weaponSound != null)
|
||||
SoundSystem.Play(weaponSound.GetSound(), Filter.Pvs(otherEntity, entityManager: EntityManager), otherEntity);
|
||||
{
|
||||
Audio.PlayPvs(weaponSound, otherEntity);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Pseudo RNG so the client can predict these.
|
||||
|
||||
@@ -31,10 +31,17 @@ public sealed class AddWhitelistCommand : IConsoleCommand
|
||||
var guid = data.UserId;
|
||||
var isWhitelisted = await db.GetWhitelistStatusAsync(guid);
|
||||
if (isWhitelisted)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("command-whitelistadd-existing", ("username", data.Username)));
|
||||
return;
|
||||
}
|
||||
|
||||
await db.AddToWhitelistAsync(guid);
|
||||
shell.WriteLine(Loc.GetString("command-whitelistadd-added", ("username", data.Username)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteError(Loc.GetString("command-whitelistadd-not-found", ("username", args[0])));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +67,17 @@ public sealed class RemoveWhitelistCommand : IConsoleCommand
|
||||
var guid = data.UserId;
|
||||
var isWhitelisted = await db.GetWhitelistStatusAsync(guid);
|
||||
if (!isWhitelisted)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("command-whitelistremove-existing", ("username", data.Username)));
|
||||
return;
|
||||
}
|
||||
|
||||
await db.RemoveFromWhitelistAsync(guid);
|
||||
shell.WriteLine(Loc.GetString("command-whitelistremove-removed", ("username", data.Username)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteError(Loc.GetString("command-whitelistremove-not-found", ("username", args[0])));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user