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