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