diff --git a/Content.Shared/ComponentTable/ComponentTableComponent.cs b/Content.Shared/ComponentTable/ComponentTableComponent.cs
new file mode 100644
index 0000000000..a402214aad
--- /dev/null
+++ b/Content.Shared/ComponentTable/ComponentTableComponent.cs
@@ -0,0 +1,19 @@
+using Content.Shared.EntityTable.EntitySelectors;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.ComponentTable;
+
+///
+/// Applies components from entities selected from the table on init.
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedComponentTableSystem))]
+public sealed partial class ComponentTableComponent : Component
+{
+ ///
+ /// The table from which to grab entities.
+ /// ALL components of the grabbed entities will be added to the holder of this component.
+ ///
+ [DataField(required: true)]
+ public EntityTableSelector Table = default!;
+}
diff --git a/Content.Shared/ComponentTable/SharedComponentTableSystem.cs b/Content.Shared/ComponentTable/SharedComponentTableSystem.cs
new file mode 100644
index 0000000000..cdb1b2dc74
--- /dev/null
+++ b/Content.Shared/ComponentTable/SharedComponentTableSystem.cs
@@ -0,0 +1,33 @@
+using Content.Shared.EntityTable;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.ComponentTable;
+
+///
+/// Applies an entity prototype to an entity on map init. Taken from entities inside an EntityTableSelector.
+///
+public sealed class SharedComponentTableSystem : EntitySystem
+{
+ [Dependency] private readonly EntityTableSystem _entTable = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnTableInit);
+ }
+
+ private void OnTableInit(Entity ent, ref MapInitEvent args)
+ {
+ var spawns = _entTable.GetSpawns(ent.Comp.Table);
+
+ foreach (var entity in spawns)
+ {
+ if (_proto.TryIndex(entity, out var entProto))
+ {
+ EntityManager.AddComponents(ent, entProto.Components);
+ }
+ }
+ }
+}
diff --git a/Content.Shared/Delivery/DeliveryModifierSystem.cs b/Content.Shared/Delivery/DeliveryModifierSystem.cs
index 505975732c..c4ad1bb3a6 100644
--- a/Content.Shared/Delivery/DeliveryModifierSystem.cs
+++ b/Content.Shared/Delivery/DeliveryModifierSystem.cs
@@ -1,4 +1,9 @@
+using Content.Shared.Examine;
+using Content.Shared.GameTicking;
+using Content.Shared.NameModifier.EntitySystems;
using Robust.Shared.Random;
+using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
namespace Content.Shared.Delivery;
@@ -8,6 +13,9 @@ namespace Content.Shared.Delivery;
public sealed partial class DeliveryModifierSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly NameModifierSystem _nameModifier = default!;
+ [Dependency] private readonly SharedDeliverySystem _delivery = default!;
public override void Initialize()
{
@@ -15,8 +23,13 @@ public sealed partial class DeliveryModifierSystem : EntitySystem
SubscribeLocalEvent(OnRandomMultiplierMapInit);
SubscribeLocalEvent(OnGetRandomMultiplier);
+
+ SubscribeLocalEvent(OnPriorityMapInit);
+ SubscribeLocalEvent(OnPriorityExamine);
+ SubscribeLocalEvent(OnGetPriorityMultiplier);
}
+ #region Random
private void OnRandomMultiplierMapInit(Entity ent, ref MapInitEvent args)
{
ent.Comp.CurrentMultiplierOffset = _random.NextFloat(ent.Comp.MinMultiplierOffset, ent.Comp.MaxMultiplierOffset);
@@ -27,4 +40,70 @@ public sealed partial class DeliveryModifierSystem : EntitySystem
{
args.AdditiveMultiplier += ent.Comp.CurrentMultiplierOffset;
}
+ #endregion
+
+ #region Priority
+ private void OnPriorityMapInit(Entity ent, ref MapInitEvent args)
+ {
+ ent.Comp.DeliverUntilTime = _timing.CurTime + ent.Comp.DeliveryTime;
+ _delivery.UpdatePriorityVisuals(ent);
+ Dirty(ent);
+ }
+
+ private void OnPriorityExamine(Entity ent, ref ExaminedEvent args)
+ {
+ var trueName = _nameModifier.GetBaseName(ent.Owner);
+ var timeLeft = ent.Comp.DeliverUntilTime - _timing.CurTime;
+
+ if (_timing.CurTime < ent.Comp.DeliverUntilTime)
+ args.PushMarkup(Loc.GetString("delivery-priority-examine", ("type", trueName), ("time", timeLeft.ToString("mm\\:ss"))));
+ else
+ args.PushMarkup(Loc.GetString("delivery-priority-expired-examine", ("type", trueName)));
+ }
+
+ private void OnGetPriorityMultiplier(Entity ent, ref GetDeliveryMultiplierEvent args)
+ {
+ if (_timing.CurTime < ent.Comp.DeliverUntilTime)
+ args.AdditiveMultiplier += ent.Comp.InTimeMultiplierOffset;
+ else
+ args.AdditiveMultiplier += ent.Comp.ExpiredMultiplierOffset;
+ }
+ #endregion
+
+ #region Update Loops
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ UpdatePriorty(frameTime);
+ }
+
+ private void UpdatePriorty(float frameTime)
+ {
+ var priorityQuery = EntityQueryEnumerator();
+ var curTime = _timing.CurTime;
+
+ while (priorityQuery.MoveNext(out var uid, out var priorityData))
+ {
+ if (priorityData.Expired)
+ continue;
+
+ if (priorityData.DeliverUntilTime < curTime)
+ {
+ priorityData.Expired = true;
+ _delivery.UpdatePriorityVisuals((uid, priorityData));
+ Dirty(uid, priorityData);
+
+ var ev = new DeliveryPriorityExpiredEvent();
+ RaiseLocalEvent(uid, ev);
+ }
+ }
+ }
+ #endregion
}
+
+///
+/// Gets raised on a priority delivery when it's timer expires.
+///
+[Serializable, NetSerializable]
+public readonly record struct DeliveryPriorityExpiredEvent;
diff --git a/Content.Shared/Delivery/DeliveryPriorityComponent.cs b/Content.Shared/Delivery/DeliveryPriorityComponent.cs
new file mode 100644
index 0000000000..769c6af0f8
--- /dev/null
+++ b/Content.Shared/Delivery/DeliveryPriorityComponent.cs
@@ -0,0 +1,43 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Delivery;
+
+///
+/// Component given to deliveries.
+/// Applies a duration before which the delivery must be delivered.
+/// If successful, adds a small multiplier, otherwise removes a small multiplier.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(DeliveryModifierSystem))]
+public sealed partial class DeliveryPriorityComponent : Component
+{
+ ///
+ /// The highest the random multiplier can go.
+ ///
+ [DataField]
+ public float InTimeMultiplierOffset = 0.2f;
+
+ ///
+ /// The lowest the random multiplier can go.
+ ///
+ [DataField]
+ public float ExpiredMultiplierOffset = -0.1f;
+
+ ///
+ /// Whether this priority delivery has already ran out of time or not.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Expired;
+
+ ///
+ /// How much time you get from spawn until the delivery expires.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan DeliveryTime = TimeSpan.FromMinutes(5);
+
+ ///
+ /// The time by which this has to be delivered.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan DeliverUntilTime;
+}
diff --git a/Content.Shared/Delivery/DeliveryVisuals.cs b/Content.Shared/Delivery/DeliveryVisuals.cs
index 69eb5fda9e..35391854be 100644
--- a/Content.Shared/Delivery/DeliveryVisuals.cs
+++ b/Content.Shared/Delivery/DeliveryVisuals.cs
@@ -9,11 +9,18 @@ public enum DeliveryVisuals : byte
IsTrash,
IsBroken,
IsFragile,
- IsPriority,
- IsPriorityInactive,
+ PriorityState,
JobIcon,
}
+[Serializable, NetSerializable]
+public enum DeliveryPriorityState : byte
+{
+ Off,
+ Active,
+ Inactive,
+}
+
[Serializable, NetSerializable]
public enum DeliverySpawnerVisuals : byte
{
diff --git a/Content.Shared/Delivery/SharedDeliverySystem.cs b/Content.Shared/Delivery/SharedDeliverySystem.cs
index 1667035dac..0f67c3459e 100644
--- a/Content.Shared/Delivery/SharedDeliverySystem.cs
+++ b/Content.Shared/Delivery/SharedDeliverySystem.cs
@@ -69,7 +69,7 @@ public abstract class SharedDeliverySystem : EntitySystem
var multiplier = GetDeliveryMultiplier(ent);
var totalSpesos = Math.Round(ent.Comp.BaseSpesoReward * multiplier);
- args.PushMarkup(Loc.GetString("delivery-earnings-examine", ("spesos", totalSpesos)));
+ args.PushMarkup(Loc.GetString("delivery-earnings-examine", ("spesos", totalSpesos)), -1);
}
}
@@ -238,7 +238,18 @@ public abstract class SharedDeliverySystem : EntitySystem
// If we're trying to unlock, always remove the priority tape
if (!isLocked)
- _appearance.SetData(uid, DeliveryVisuals.IsPriority, false);
+ _appearance.SetData(uid, DeliveryVisuals.PriorityState, DeliveryPriorityState.Off);
+ }
+
+ public void UpdatePriorityVisuals(Entity ent)
+ {
+ if (!TryComp(ent, out var delivery))
+ return;
+
+ if (delivery.IsLocked && !delivery.IsOpened)
+ {
+ _appearance.SetData(ent, DeliveryVisuals.PriorityState, ent.Comp.Expired ? DeliveryPriorityState.Inactive : DeliveryPriorityState.Active);
+ }
}
protected void UpdateDeliverySpawnerVisuals(EntityUid uid, int contents)
@@ -257,7 +268,10 @@ public abstract class SharedDeliverySystem : EntitySystem
var ev = new GetDeliveryMultiplierEvent();
RaiseLocalEvent(ent, ref ev);
- return ev.AdditiveMultiplier * ev.MultiplicativeMultiplier;
+ // Ensure the multiplier can never go below 0.
+ var totalMultiplier = Math.Max(ev.AdditiveMultiplier * ev.MultiplicativeMultiplier, 0);
+
+ return totalMultiplier;
}
protected virtual void GrantSpesoReward(Entity ent) { }
diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs
index 18a98c7a0c..34677966f8 100644
--- a/Content.Shared/Item/SharedItemSystem.cs
+++ b/Content.Shared/Item/SharedItemSystem.cs
@@ -141,7 +141,8 @@ public abstract class SharedItemSystem : EntitySystem
{
// show at end of message generally
args.PushMarkup(Loc.GetString("item-component-on-examine-size",
- ("size", GetItemSizeLocale(component.Size))), priority: -1);
+ ("size", GetItemSizeLocale(component.Size))),
+ priority: -2);
}
public ItemSizePrototype GetSizePrototype(ProtoId id)
diff --git a/Resources/Locale/en-US/delivery/delivery-component.ftl b/Resources/Locale/en-US/delivery/delivery-component.ftl
index 5903094690..c7f2347da4 100644
--- a/Resources/Locale/en-US/delivery/delivery-component.ftl
+++ b/Resources/Locale/en-US/delivery/delivery-component.ftl
@@ -20,3 +20,8 @@ delivery-teleporter-amount-examine =
}
delivery-teleporter-empty = The {$entity} is empty.
delivery-teleporter-empty-verb = Take mail
+
+
+# modifiers
+delivery-priority-examine = This is a [color=orange]priority {$type}[/color]. You have [color=orange]{$time}[/color] left to deliver it to get a bonus.
+delivery-priority-expired-examine = This is a [color=orange]priority {$type}[/color]. It seems you ran out of time.
diff --git a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
index 2ff5e9daf7..e90a99510f 100644
--- a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
+++ b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
@@ -14,14 +14,11 @@
enum.DeliveryVisualLayers.Lock:
True: { visible: true }
False: { visible: false }
- enum.DeliveryVisuals.IsPriority:
+ enum.DeliveryVisuals.PriorityState:
enum.DeliveryVisualLayers.PriorityTape:
- True: { visible: true }
- False: { visible: false }
- enum.DeliveryVisuals.IsPriorityInactive:
- enum.DeliveryVisualLayers.PriorityTape:
- True: { shader: shaded, state: priority_inactive }
- False: { shader: unshaded, state: priority }
+ Off: { visible: false }
+ Active: { visible: true, shader: unshaded, state: priority }
+ Inactive: { visible: true, shader: shaded, state: priority_inactive }
enum.DeliveryVisuals.IsBroken:
enum.DeliveryVisualLayers.Breakage:
True: { visible: true }
@@ -45,6 +42,9 @@
- type: SimpleToolUsage
doAfter: 4
usageVerb: delivery-slice-verb
+ - type: ComponentTable
+ table: !type:NestedSelector
+ tableId: DeliveryModifierTable
- type: entity
parent: BaseDelivery
@@ -128,3 +128,19 @@
containers:
delivery: !type:NestedSelector
tableId: LetterDeliveryRewards
+
+
+# Modifier Tables
+- type: entityTable
+ id: DeliveryModifierTable
+ table: !type:AllSelector
+ children:
+ - id: DeliveryModifierPriority
+ prob: 0.25
+
+- type: entity
+ id: DeliveryModifierPriority
+ description: Components to add when a delivery is rolled as priority.
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: DeliveryPriority