diff --git a/Content.Server/Advertise/AdvertiseComponent.cs b/Content.Server/Advertise/AdvertiseComponent.cs
index b57e0e3aa8..c190cd1f69 100644
--- a/Content.Server/Advertise/AdvertiseComponent.cs
+++ b/Content.Server/Advertise/AdvertiseComponent.cs
@@ -1,144 +1,49 @@
-using System.Collections.Generic;
-using System.Threading;
+using System;
using Content.Server.Advertisements;
-using Content.Server.Chat.Managers;
+using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Log;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.ViewVariables;
namespace Content.Server.Advertise
{
- [RegisterComponent]
+ [RegisterComponent, Friend(typeof(AdvertiseSystem))]
public class AdvertiseComponent : Component
{
public override string Name => "Advertise";
- private CancellationTokenSource _cancellationSource = new();
-
///
- /// Minimum time to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
+ /// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
///
[ViewVariables]
[DataField("minWait")]
- private int MinWait { get; } = 480; // 8 minutes
+ public int MinimumWait { get; } = 8 * 60;
///
- /// Maximum time to wait before saying a new ad, in seconds. Has to be larger than or equal
- /// to
+ /// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal
+ /// to
///
[ViewVariables]
[DataField("maxWait")]
- private int MaxWait { get; } = 600; // 10 minutes
-
- [DataField("pack")]
- private string PackPrototypeId { get; } = string.Empty;
-
- private List _advertisements = new();
-
- protected override void Initialize()
- {
- base.Initialize();
-
- IoCManager.Resolve().TryIndex(PackPrototypeId, out AdvertisementsPackPrototype? packPrototype);
-
- // Load advertisements pack
- if (string.IsNullOrEmpty(PackPrototypeId) || packPrototype == null)
- {
- // If there is no pack, log a warning and don't start timer
- Logger.Warning($"{Owner} has {Name}Component but no advertisments pack.");
- return;
- }
-
- _advertisements = packPrototype.Advertisements;
-
- // Do not start timer if advertisement list is empty
- if (_advertisements.Count == 0)
- {
- // If no advertisements could be loaded, log a warning and don't start timer
- Logger.Warning($"{Owner} tried to load advertisements pack without ads.");
- return;
- }
-
- // Throw exception if MinWait is smaller than 1.
- if (MinWait < 1)
- {
- throw new PrototypeLoadException($"{Owner} has illegal minWait for {Name}Component: {MinWait}.");
- }
-
- // Throw exception if MinWait larger than MaxWait.
- if (MinWait > MaxWait)
- {
- throw new PrototypeLoadException($"{Owner} should have minWait greater than or equal to maxWait for {Name}Component.");
- }
-
- // Start timer at initialization, without MinWait bound
- RefreshTimer(false);
- }
-
- protected override void OnRemove()
- {
- _cancellationSource.Cancel();
- _cancellationSource.Dispose();
-
- base.OnRemove();
- }
+ public int MaximumWait { get; } = 10 * 60;
///
- /// Say advertisement and restart timer.
+ /// The identifier for the advertisements pack prototype.
///
- private void SayAndRefresh()
- {
- IRobustRandom random = IoCManager.Resolve();
- IChatManager chatManager = IoCManager.Resolve();
-
- // Say advertisement
- chatManager.EntitySay(Owner, Loc.GetString(random.Pick(_advertisements)));
-
- // Refresh timer to repeat cycle
- RefreshTimer();
- }
+ [DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer))]
+ public string PackPrototypeId { get; } = string.Empty;
///
- /// Refresh cancellation token and spawn new timer with random wait between
- /// and .
- ///
- /// Whether should be used to have a minimum waiting time.
- ///
+ /// The next time an advertisement will be said.
///
- private void RefreshTimer(bool minBound = true)
- {
- // Generate new source
- _cancellationSource.Cancel();
- _cancellationSource.Dispose();
- _cancellationSource = new CancellationTokenSource();
-
- // Generate random wait time, then create timer
- IRobustRandom random = IoCManager.Resolve();
- var wait = minBound ? random.Next(MinWait * 1000, MaxWait * 1000) : random.Next(MaxWait * 1000);
- Owner.SpawnTimer(wait, SayAndRefresh, _cancellationSource.Token);
- }
+ [ViewVariables]
+ public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero;
///
- /// Pause the advertising until is called.
+ /// Whether the entity will say advertisements or not.
///
- public void Pause()
- {
- // Cancel current timer
- _cancellationSource.Cancel();
- }
-
- ///
- /// Resume the advertising after pausing.
- ///
- public void Resume()
- {
- // Restart timer, without minBound
- RefreshTimer(false);
- }
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool Enabled { get; set; } = true;
}
}
diff --git a/Content.Server/Advertise/AdvertiseSystem.cs b/Content.Server/Advertise/AdvertiseSystem.cs
new file mode 100644
index 0000000000..62803c5a10
--- /dev/null
+++ b/Content.Server/Advertise/AdvertiseSystem.cs
@@ -0,0 +1,136 @@
+using System;
+using Content.Server.Advertisements;
+using Content.Server.Chat.Managers;
+using Content.Server.Power.Components;
+using Content.Server.VendingMachines;
+using Content.Shared.Acts;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Advertise
+{
+ public class AdvertiseSystem : EntitySystem
+ {
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+
+ private const float UpdateTimer = 5f;
+
+ private float _timer = 0f;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnPowerChanged);
+
+ SubscribeLocalEvent(OnPowerReceiverEnableChangeAttempt);
+ SubscribeLocalEvent(OnVendingEnableChangeAttempt);
+ }
+
+ private void OnComponentInit(EntityUid uid, AdvertiseComponent advertise, ComponentInit args)
+ {
+ RefreshTimer(uid, true, advertise);
+ }
+
+ private void OnPowerChanged(EntityUid uid, AdvertiseComponent advertise, PowerChangedEvent args)
+ {
+ SetEnabled(uid, args.Powered, advertise);
+ }
+
+ public void RefreshTimer(EntityUid uid, bool minimumBound = true, AdvertiseComponent? advertise = null)
+ {
+ if (!Resolve(uid, ref advertise))
+ return;
+
+ var minWait = Math.Max(1, advertise.MinimumWait);
+ var maxWait = Math.Max(minWait, advertise.MaximumWait);
+
+ var waitSeconds = minimumBound ? _random.Next(minWait, maxWait) : _random.Next(maxWait);
+ advertise.NextAdvertisementTime = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(waitSeconds));
+ }
+
+ public void SayAdvertisement(EntityUid uid, bool refresh = true, AdvertiseComponent? advertise = null)
+ {
+ if (!Resolve(uid, ref advertise))
+ return;
+
+ if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements))
+ _chatManager.EntitySay(advertise.Owner, Loc.GetString(_random.Pick(advertisements.Advertisements)));
+
+ if(refresh)
+ RefreshTimer(uid, true, advertise);
+ }
+
+ public void SetEnabled(EntityUid uid, bool enabled, AdvertiseComponent? advertise = null)
+ {
+ if (!Resolve(uid, ref advertise))
+ return;
+
+ var attemptEvent = new AdvertiseEnableChangeAttemptEvent(enabled, advertise.Enabled);
+ RaiseLocalEvent(uid, attemptEvent, false);
+
+ if (attemptEvent.Cancelled)
+ return;
+
+ if(enabled)
+ RefreshTimer(uid, !advertise.Enabled, advertise);
+
+ advertise.Enabled = enabled;
+ }
+
+ private void OnPowerReceiverEnableChangeAttempt(EntityUid uid, ApcPowerReceiverComponent component, AdvertiseEnableChangeAttemptEvent args)
+ {
+ if(args.NewState && !component.Powered)
+ args.Cancel();
+ }
+
+ private void OnVendingEnableChangeAttempt(EntityUid uid, VendingMachineComponent component, AdvertiseEnableChangeAttemptEvent args)
+ {
+ // TODO: Improve this...
+ if(args.NewState && component.Broken)
+ args.Cancel();
+ }
+
+ public override void Update(float frameTime)
+ {
+ _timer += frameTime;
+
+ if (_timer < UpdateTimer)
+ return;
+
+ _timer -= UpdateTimer;
+
+ var curTime = _gameTiming.CurTime;
+
+ foreach (var advertise in ComponentManager.EntityQuery())
+ {
+ if (!advertise.Enabled)
+ continue;
+
+ // If it's still not time for the advertisement, do nothing.
+ if (advertise.NextAdvertisementTime > curTime)
+ continue;
+
+ SayAdvertisement(advertise.Owner.Uid, true, advertise);
+ }
+ }
+ }
+
+ public class AdvertiseEnableChangeAttemptEvent : CancellableEntityEventArgs
+ {
+ public bool NewState { get; }
+ public bool OldState { get; }
+
+ public AdvertiseEnableChangeAttemptEvent(bool newState, bool oldEnabledState)
+ {
+ NewState = newState;
+ OldState = oldEnabledState;
+ }
+ }
+}
diff --git a/Content.Server/VendingMachines/VendingMachineComponent.cs b/Content.Server/VendingMachines/VendingMachineComponent.cs
index be132f2f50..c0bfb232cf 100644
--- a/Content.Server/VendingMachines/VendingMachineComponent.cs
+++ b/Content.Server/VendingMachines/VendingMachineComponent.cs
@@ -52,6 +52,8 @@ namespace Content.Server.VendingMachines
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(VendingMachineUiKey.Key);
+ public bool Broken => _broken;
+
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if(!eventArgs.User.TryGetComponent(out ActorComponent? actor))
@@ -129,18 +131,6 @@ namespace Content.Server.VendingMachines
{
var state = args.Powered ? VendingMachineVisualState.Normal : VendingMachineVisualState.Off;
TrySetVisualState(state);
-
- // Pause/resume advertising if advertising component exists and not broken
- if (!Owner.TryGetComponent(out AdvertiseComponent? advertiseComponent) || _broken) return;
-
- if (Powered)
- {
- advertiseComponent.Resume();
- }
- else
- {
- advertiseComponent.Pause();
- }
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
@@ -250,11 +240,6 @@ namespace Content.Server.VendingMachines
{
_broken = true;
TrySetVisualState(VendingMachineVisualState.Broken);
-
- if (Owner.TryGetComponent(out AdvertiseComponent? advertiseComponent))
- {
- advertiseComponent.Pause();
- }
}
public enum Wires