diff --git a/Content.Server/Delivery/DeliverySystem.cs b/Content.Server/Delivery/DeliverySystem.cs
index 329e3d11ed..0ddfde49ae 100644
--- a/Content.Server/Delivery/DeliverySystem.cs
+++ b/Content.Server/Delivery/DeliverySystem.cs
@@ -1,13 +1,16 @@
using Content.Server.Cargo.Systems;
+using Content.Server.Chat.Systems;
using Content.Server.Station.Systems;
using Content.Server.StationRecords.Systems;
using Content.Shared.Cargo.Components;
+using Content.Shared.Cargo.Prototypes;
using Content.Shared.Delivery;
using Content.Shared.FingerprintReader;
using Content.Shared.Labels.EntitySystems;
using Content.Shared.StationRecords;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
namespace Content.Server.Delivery;
@@ -25,6 +28,13 @@ public sealed partial class DeliverySystem : SharedDeliverySystem
[Dependency] private readonly FingerprintReaderSystem _fingerprintReader = default!;
[Dependency] private readonly LabelSystem _label = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly IPrototypeManager _protoMan = default!;
+
+ ///
+ /// Default reason to use if the penalization is triggered
+ ///
+ private static readonly LocId DefaultMessage = "delivery-penalty-default-reason";
public override void Initialize()
{
@@ -39,19 +49,15 @@ public sealed partial class DeliverySystem : SharedDeliverySystem
{
_container.EnsureContainer(ent, ent.Comp.Container);
- var stationId = _station.GetStationInMap(Transform(ent).MapID);
-
- if (stationId == null)
+ if (_station.GetStationInMap(Transform(ent).MapID) is not { } stationId)
return;
- _records.TryGetRandomRecord(stationId.Value, out var entry);
-
- if (entry == null)
+ if (!_records.TryGetRandomRecord(stationId, out var entry))
return;
ent.Comp.RecipientName = entry.Name;
ent.Comp.RecipientJobTitle = entry.JobTitle;
- ent.Comp.RecipientStation = stationId.Value;
+ ent.Comp.RecipientStation = stationId;
_appearance.SetData(ent, DeliveryVisuals.JobIcon, entry.JobIcon);
@@ -73,10 +79,74 @@ public sealed partial class DeliverySystem : SharedDeliverySystem
if (!TryComp(ent.Comp.RecipientStation, out var account))
return;
+ var stationAccountEnt = (ent.Comp.RecipientStation.Value, account);
+
+ var multiplier = GetDeliveryMultiplier(ent!); // Resolve so we know it's got the component
+
_cargo.UpdateBankAccount(
- (ent.Comp.RecipientStation.Value, account),
- ent.Comp.SpesoReward,
- _cargo.CreateAccountDistribution((ent.Comp.RecipientStation.Value, account)));
+ stationAccountEnt,
+ (int)(ent.Comp.BaseSpesoReward * multiplier),
+ _cargo.CreateAccountDistribution((ent.Comp.RecipientStation.Value, account)));
+ }
+
+ ///
+ /// Runs the penalty logic: Announcing the penalty and calculating how much to charge the designated account
+ ///
+ /// The delivery for which to run the penalty.
+ /// The penalty reason, displayed in front of the message.
+ protected override void HandlePenalty(Entity ent, string? reason = null)
+ {
+ if (!TryComp(ent.Comp.RecipientStation, out var stationAccount))
+ return;
+
+ if (ent.Comp.WasPenalized)
+ return;
+
+ if (!_protoMan.TryIndex(ent.Comp.PenaltyBankAccount, out var accountInfo))
+ return;
+
+ var multiplier = GetDeliveryMultiplier(ent);
+
+ var localizedAccountName = Loc.GetString(accountInfo.Name);
+
+ reason ??= Loc.GetString(DefaultMessage);
+
+ var dist = new Dictionary, double>()
+ {
+ { ent.Comp.PenaltyBankAccount, 1.0 }
+ };
+
+ var penaltyAccountBalance = stationAccount.Accounts[ent.Comp.PenaltyBankAccount];
+ var calculatedPenalty = (int)(ent.Comp.BaseSpesoPenalty * multiplier);
+
+ // Prevents cargo from going into negatives
+ if (calculatedPenalty > penaltyAccountBalance )
+ calculatedPenalty = Math.Max(0, penaltyAccountBalance);
+
+ _cargo.UpdateBankAccount(
+ (ent.Comp.RecipientStation.Value, stationAccount),
+ -calculatedPenalty,
+ dist);
+
+ var message = Loc.GetString("delivery-penalty-message", ("reason", reason), ("spesos", calculatedPenalty), ("account", localizedAccountName.ToUpper()));
+ _chat.TrySendInGameICMessage(ent, message, InGameICChatType.Speak, hideChat: true);
+
+ ent.Comp.WasPenalized = true;
+ DirtyField(ent.Owner, ent.Comp, nameof(DeliveryComponent.WasPenalized));
+ }
+
+ ///
+ /// Gathers the total multiplier for a delivery.
+ /// This is done by components having subscribed to GetDeliveryMultiplierEvent and having added onto it.
+ ///
+ /// The delivery for which to get the multiplier.
+ /// Total multiplier.
+ private float GetDeliveryMultiplier(Entity ent)
+ {
+ var ev = new GetDeliveryMultiplierEvent();
+ RaiseLocalEvent(ent, ref ev);
+
+ return ev.Multiplier;
}
public override void Update(float frameTime)
diff --git a/Content.Shared/Delivery/DeliveryComponent.cs b/Content.Shared/Delivery/DeliveryComponent.cs
index 19effcd211..3eabb5165c 100644
--- a/Content.Shared/Delivery/DeliveryComponent.cs
+++ b/Content.Shared/Delivery/DeliveryComponent.cs
@@ -1,5 +1,7 @@
+using Content.Shared.Cargo.Prototypes;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
namespace Content.Shared.Delivery;
@@ -23,10 +25,16 @@ public sealed partial class DeliveryComponent : Component
public bool IsLocked = true;
///
- /// The amount of spesos that gets added to the station bank account on unlock.
+ /// The base amount of spesos that gets added to the station bank account on unlock.
///
[DataField, AutoNetworkedField]
- public int SpesoReward = 500;
+ public int BaseSpesoReward = 500;
+
+ ///
+ /// The base amount of spesos that will be removed from the station bank account on a penalized delivery
+ ///
+ [DataField, AutoNetworkedField]
+ public int BaseSpesoPenalty = 250;
///
/// The name of the recipient of this delivery.
@@ -48,6 +56,19 @@ public sealed partial class DeliveryComponent : Component
[DataField, AutoNetworkedField]
public EntityUid? RecipientStation;
+ ///
+ /// The bank account ID of the account to subtract funds from in case of penalization
+ ///
+ [DataField, AutoNetworkedField]
+ public ProtoId PenaltyBankAccount = "Cargo";
+
+ ///
+ /// Whether this delivery has already received a penalty.
+ /// Used to avoid getting penalized several times.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool WasPenalized;
+
///
/// The sound to play when the delivery is unlocked.
///
diff --git a/Content.Shared/Delivery/DeliverySpawnerComponent.cs b/Content.Shared/Delivery/DeliverySpawnerComponent.cs
index 9ed3ff1b91..81e8c97cf5 100644
--- a/Content.Shared/Delivery/DeliverySpawnerComponent.cs
+++ b/Content.Shared/Delivery/DeliverySpawnerComponent.cs
@@ -1,8 +1,6 @@
using Content.Shared.EntityTable.EntitySelectors;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-
namespace Content.Shared.Delivery;
///
diff --git a/Content.Shared/Delivery/SharedDeliverySystem.cs b/Content.Shared/Delivery/SharedDeliverySystem.cs
index 8d343b4acb..ffbf520610 100644
--- a/Content.Shared/Delivery/SharedDeliverySystem.cs
+++ b/Content.Shared/Delivery/SharedDeliverySystem.cs
@@ -210,9 +210,21 @@ public abstract class SharedDeliverySystem : EntitySystem
protected virtual void GrantSpesoReward(Entity ent) { }
+ protected virtual void HandlePenalty(Entity ent, string? reason = null) { }
+
protected virtual void SpawnDeliveries(Entity ent) { }
}
+///
+/// Used to gather the multiplier from all different delivery components.
+///
+[ByRefEvent]
+public record struct GetDeliveryMultiplierEvent(float Multiplier)
+{
+ // we can't use an optional parameter because the default parameterless constructor defaults everything
+ public GetDeliveryMultiplierEvent() : this(1.0f) { }
+}
+
///
/// Event raised on the delivery when it is unlocked.
///
diff --git a/Resources/Locale/en-US/delivery/delivery-messages.ftl b/Resources/Locale/en-US/delivery/delivery-messages.ftl
new file mode 100644
index 0000000000..50248e517a
--- /dev/null
+++ b/Resources/Locale/en-US/delivery/delivery-messages.ftl
@@ -0,0 +1,4 @@
+delivery-penalty-default-reason = WARNING
+delivery-penalty-default-account-name = UNKNOWN ACCOUNT
+
+delivery-penalty-message = {$reason}! INVOKING A PENALTY OF {$spesos} SPESOS ON {$account}!
diff --git a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
index 2706cf380c..b396b20a46 100644
--- a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
+++ b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
@@ -74,7 +74,10 @@
- type: Item
size: Huge
- type: Delivery
- spesoReward: 1000
+ baseSpesoReward: 1000
+ baseSpesoPenalty: 500
+ - type: Speech
+ speechVerb: Robotic
- type: EntityTableContainerFill
containers:
delivery: !type:NestedSelector
@@ -111,7 +114,10 @@
- type: Item
storedRotation: 90
- type: Delivery
- spesoReward: 500
+ baseSpesoReward: 500
+ baseSpesoPenalty: 250
+ - type: Speech
+ speechVerb: Robotic
- type: EntityTableContainerFill
containers:
delivery: !type:NestedSelector