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