]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Replace `AdvertiseComponent` with `DatasetVocalizerComponent` (#38887)
authorTayrtahn <tayrtahn@gmail.com>
Thu, 10 Jul 2025 18:12:24 +0000 (14:12 -0400)
committerGitHub <noreply@github.com>
Thu, 10 Jul 2025 18:12:24 +0000 (11:12 -0700)
* Replace AdvertiseComponent with DatasetVocalizerComponent

* No vocalizing while broken or without power

* Kill AdvertiseComponent/System

* This really shouldn't be here

* xmldoc for VocalizerRequiresPowerComponent

* TryIndex -> Index

14 files changed:
Content.Server/Advertise/Components/AdvertiseComponent.cs [deleted file]
Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs [deleted file]
Content.Server/VendingMachines/VendingMachineSystem.cs
Content.Server/Vocalization/Components/DatasetVocalizerComponent.cs [new file with mode: 0644]
Content.Server/Vocalization/Components/VocalizerComponent.cs
Content.Server/Vocalization/Components/VocalizerRequiresPowerComponent.cs [new file with mode: 0644]
Content.Server/Vocalization/Systems/DatasetVocalizationSystem.cs [new file with mode: 0644]
Content.Server/Vocalization/Systems/VocalizationSystem.cs
Resources/Maps/Nonstations/dm01-entryway.yml
Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml
Resources/Prototypes/Entities/Structures/Machines/fatextractor.yml
Resources/Prototypes/Entities/Structures/Machines/smartfridge.yml
Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml

diff --git a/Content.Server/Advertise/Components/AdvertiseComponent.cs b/Content.Server/Advertise/Components/AdvertiseComponent.cs
deleted file mode 100644 (file)
index 9465023..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-using Content.Server.Advertise.EntitySystems;
-using Content.Shared.Dataset;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Advertise.Components;
-
-/// <summary>
-/// Makes this entity periodically advertise by speaking a randomly selected
-/// message from a specified dataset into local chat.
-/// </summary>
-[RegisterComponent, Access(typeof(AdvertiseSystem))]
-public sealed partial class AdvertiseComponent : Component
-{
-    /// <summary>
-    /// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
-    /// </summary>
-    [DataField]
-    public int MinimumWait { get; private set; } = 8 * 60;
-
-    /// <summary>
-    /// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal
-    /// to <see cref="MinimumWait"/>
-    /// </summary>
-    [DataField]
-    public int MaximumWait { get; private set; } = 10 * 60;
-
-    /// <summary>
-    /// If true, the delay before the first advertisement (at MapInit) will ignore <see cref="MinimumWait"/>
-    /// and instead be rolled between 0 and <see cref="MaximumWait"/>. This only applies to the initial delay;
-    /// <see cref="MinimumWait"/> will be respected after that.
-    /// </summary>
-    [DataField]
-    public bool Prewarm = true;
-
-    /// <summary>
-    /// The identifier for the advertisements dataset prototype.
-    /// </summary>
-    [DataField(required: true)]
-    public ProtoId<LocalizedDatasetPrototype> Pack { get; private set; }
-
-    /// <summary>
-    /// The next time an advertisement will be said.
-    /// </summary>
-    [DataField]
-    public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero;
-
-}
diff --git a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs
deleted file mode 100644 (file)
index 7f2e128..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-using Content.Server.Advertise.Components;
-using Content.Server.Chat.Systems;
-using Content.Server.Power.Components;
-using Content.Shared.VendingMachines;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Advertise.EntitySystems;
-
-public sealed class AdvertiseSystem : EntitySystem
-{
-    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly IGameTiming _gameTiming = default!;
-    [Dependency] private readonly ChatSystem _chat = default!;
-
-    /// <summary>
-    /// The maximum amount of time between checking if advertisements should be displayed
-    /// </summary>
-    private readonly TimeSpan _maximumNextCheckDuration = TimeSpan.FromSeconds(15);
-
-    /// <summary>
-    /// The next time the game will check if advertisements should be displayed
-    /// </summary>
-    private TimeSpan _nextCheckTime = TimeSpan.MinValue;
-
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<AdvertiseComponent, MapInitEvent>(OnMapInit);
-
-        SubscribeLocalEvent<ApcPowerReceiverComponent, AttemptAdvertiseEvent>(OnPowerReceiverAttemptAdvertiseEvent);
-        SubscribeLocalEvent<VendingMachineComponent, AttemptAdvertiseEvent>(OnVendingAttemptAdvertiseEvent);
-
-        _nextCheckTime = TimeSpan.MinValue;
-    }
-
-    private void OnMapInit(EntityUid uid, AdvertiseComponent advert, MapInitEvent args)
-    {
-        var prewarm = advert.Prewarm;
-        RandomizeNextAdvertTime(advert, prewarm);
-        _nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
-    }
-
-    private void RandomizeNextAdvertTime(AdvertiseComponent advert, bool prewarm = false)
-    {
-        var minDuration = prewarm ? 0 : Math.Max(1, advert.MinimumWait);
-        var maxDuration = Math.Max(minDuration, advert.MaximumWait);
-        var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration));
-
-        advert.NextAdvertisementTime = _gameTiming.CurTime + waitDuration;
-    }
-
-    public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advert = null)
-    {
-        if (!Resolve(uid, ref advert))
-            return;
-
-        var attemptEvent = new AttemptAdvertiseEvent(uid);
-        RaiseLocalEvent(uid, ref attemptEvent);
-        if (attemptEvent.Cancelled)
-            return;
-
-        if (_prototypeManager.TryIndex(advert.Pack, out var advertisements))
-            _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Values)), InGameICChatType.Speak, hideChat: true);
-    }
-
-    public override void Update(float frameTime)
-    {
-        var currentGameTime = _gameTiming.CurTime;
-        if (_nextCheckTime > currentGameTime)
-            return;
-
-        // _nextCheckTime starts at TimeSpan.MinValue, so this has to SET the value, not just increment it.
-        _nextCheckTime = currentGameTime + _maximumNextCheckDuration;
-
-        var query = EntityQueryEnumerator<AdvertiseComponent>();
-        while (query.MoveNext(out var uid, out var advert))
-        {
-            if (currentGameTime > advert.NextAdvertisementTime)
-            {
-                SayAdvertisement(uid, advert);
-                // The timer is always refreshed when it expires, to prevent mass advertising (ex: all the vending machines have no power, and get it back at the same time).
-                RandomizeNextAdvertTime(advert);
-            }
-            _nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
-        }
-    }
-
-
-    private static void OnPowerReceiverAttemptAdvertiseEvent(EntityUid uid, ApcPowerReceiverComponent powerReceiver, ref AttemptAdvertiseEvent args)
-    {
-        args.Cancelled |= !powerReceiver.Powered;
-    }
-
-    private static void OnVendingAttemptAdvertiseEvent(EntityUid uid, VendingMachineComponent machine, ref AttemptAdvertiseEvent args)
-    {
-        args.Cancelled |= machine.Broken;
-    }
-}
-
-[ByRefEvent]
-public record struct AttemptAdvertiseEvent(EntityUid? Advertiser)
-{
-    public bool Cancelled = false;
-}
index fda8f0682027f21cb94437143d1aca83070fd256..23a744ddd68d92d131dc1d0fb047a75560e972f4 100644 (file)
@@ -4,6 +4,7 @@ using Content.Server.Cargo.Systems;
 using Content.Server.Emp;
 using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
+using Content.Server.Vocalization.Systems;
 using Content.Shared.Cargo;
 using Content.Shared.Damage;
 using Content.Shared.Destructible;
@@ -42,6 +43,7 @@ namespace Content.Server.VendingMachines
             SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
             SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
             SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
+            SubscribeLocalEvent<VendingMachineComponent, TryVocalizeEvent>(OnTryVocalize);
 
             SubscribeLocalEvent<VendingMachineComponent, ActivatableUIOpenAttemptEvent>(OnActivatableUIOpenAttempt);
 
@@ -309,5 +311,10 @@ namespace Content.Server.VendingMachines
                 component.NextEmpEject = _timing.CurTime;
             }
         }
+
+        private void OnTryVocalize(Entity<VendingMachineComponent> ent, ref TryVocalizeEvent args)
+        {
+            args.Cancelled |= ent.Comp.Broken;
+        }
     }
 }
diff --git a/Content.Server/Vocalization/Components/DatasetVocalizerComponent.cs b/Content.Server/Vocalization/Components/DatasetVocalizerComponent.cs
new file mode 100644 (file)
index 0000000..c64fe01
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Dataset;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Vocalization.Components;
+
+/// <summary>
+/// A simple message provider for <see cref="VocalizationSystem"/> that randomly selects
+/// messages from a <see cref="LocalizedDatasetPrototype"/>.
+/// </summary>
+[RegisterComponent]
+public sealed partial class DatasetVocalizerComponent : Component
+{
+    /// <summary>
+    /// ID of the <see cref="LocalizedDatasetPrototype"/> that will provide messages.
+    /// </summary>
+    [DataField]
+    public ProtoId<LocalizedDatasetPrototype> Dataset;
+}
index 0dfde9751b0fba6c214edf8cdba7d77f1e26e433..f5bb37a3357c94771f059f65a9a9b6352f1a6cb0 100644 (file)
@@ -27,4 +27,11 @@ public sealed partial class VocalizerComponent : Component
     [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
     [AutoPausedField]
     public TimeSpan NextVocalizeInterval = TimeSpan.Zero;
+
+    /// <summary>
+    /// If true, messages spoken by this vocalizer will not be logged in the chat window
+    /// and will only be shown as speech bubbles.
+    /// </summary>
+    [DataField]
+    public bool HideChat;
 }
diff --git a/Content.Server/Vocalization/Components/VocalizerRequiresPowerComponent.cs b/Content.Server/Vocalization/Components/VocalizerRequiresPowerComponent.cs
new file mode 100644 (file)
index 0000000..69de671
--- /dev/null
@@ -0,0 +1,9 @@
+namespace Content.Server.Vocalization.Components;
+
+/// <summary>
+/// Used in combination with <see cref="VocalizerComponent"/>.
+/// Blocks any attempts to vocalize if the entity has an <see cref="ApcPowerReceiverComponent"/>
+/// and is currently unpowered.
+/// </summary>
+[RegisterComponent]
+public sealed partial class VocalizerRequiresPowerComponent : Component;
diff --git a/Content.Server/Vocalization/Systems/DatasetVocalizationSystem.cs b/Content.Server/Vocalization/Systems/DatasetVocalizationSystem.cs
new file mode 100644 (file)
index 0000000..fcca560
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Server.Vocalization.Components;
+using Content.Shared.Random.Helpers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Vocalization.Systems;
+
+/// <inheritdoc cref="DatasetVocalizerComponent"/>
+public sealed class DatasetVocalizationSystem : EntitySystem
+{
+    [Dependency] private readonly IPrototypeManager _protoMan = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DatasetVocalizerComponent, TryVocalizeEvent>(OnTryVocalize);
+    }
+
+    private void OnTryVocalize(Entity<DatasetVocalizerComponent> ent, ref TryVocalizeEvent args)
+    {
+        if (args.Handled)
+            return;
+
+        var dataset = _protoMan.Index(ent.Comp.Dataset);
+
+        args.Message = _random.Pick(dataset);
+        args.Handled = true;
+    }
+}
index b0a2e232a4e59cc11f4858c60f07f2dc995fd864..49dfaf428198218f1eb91268e190fb550e65fc7b 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Chat.Systems;
+using Content.Server.Power.Components;
 using Content.Server.Vocalization.Components;
 using Content.Shared.ActionBlocker;
 using Robust.Shared.Random;
@@ -18,6 +19,27 @@ public sealed partial class VocalizationSystem : EntitySystem
     [Dependency] private readonly IGameTiming _gameTiming = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
 
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<VocalizerComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<VocalizerRequiresPowerComponent, TryVocalizeEvent>(OnRequiresPowerTryVocalize);
+    }
+
+    private void OnMapInit(Entity<VocalizerComponent> ent, ref MapInitEvent args)
+    {
+        ent.Comp.NextVocalizeInterval = _random.Next(ent.Comp.MinVocalizeInterval, ent.Comp.MaxVocalizeInterval);
+    }
+
+    private void OnRequiresPowerTryVocalize(Entity<VocalizerRequiresPowerComponent> ent, ref TryVocalizeEvent args)
+    {
+        if (!TryComp<ApcPowerReceiverComponent>(ent, out var receiver))
+            return;
+
+        args.Cancelled |= !receiver.Powered;
+    }
+
     /// <summary>
     /// Try speaking by raising a TryVocalizeEvent
     /// This event is passed to systems adding a message to it and setting it to handled
@@ -27,6 +49,10 @@ public sealed partial class VocalizationSystem : EntitySystem
         var tryVocalizeEvent = new TryVocalizeEvent();
         RaiseLocalEvent(entity.Owner, ref tryVocalizeEvent);
 
+        // If the event was cancelled, don't speak
+        if (tryVocalizeEvent.Cancelled)
+            return;
+
         // if the event was never handled, return
         // this happens if there are no components that trigger systems to add a message to this event
         if (!tryVocalizeEvent.Handled)
@@ -60,7 +86,7 @@ public sealed partial class VocalizationSystem : EntitySystem
             return;
 
         // send the message
-        _chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, ChatTransmitRange.Normal);
+        _chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, entity.Comp.HideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal);
     }
 
     public override void Update(float frameTime)
@@ -99,7 +125,7 @@ public sealed partial class VocalizationSystem : EntitySystem
 /// <param name="Message">Message to send, this is null when the event is just fired and should be set by a system</param>
 /// <param name="Handled">Whether the message was handled by a system</param>
 [ByRefEvent]
-public record struct TryVocalizeEvent(string? Message = null, bool Handled = false);
+public record struct TryVocalizeEvent(string? Message = null, bool Handled = false, bool Cancelled = false);
 
 /// <summary>
 /// Fired when the entity wants to vocalize and has a message. Allows for interception by other systems if the
index bb252e35abe919b121a208594db5982c28d69f32..2f15189a67847f60e3955bd9740ee7a33169bc4d 100644 (file)
@@ -3958,8 +3958,6 @@ entities:
     - type: Transform
       pos: 8.5,27.5
       parent: 2
-    - type: Advertise
-      nextAdvertisementTime: 6277.3942716
 - proto: WallPlastitaniumDiagonalIndestructible
   entities:
   - uid: 171
index e0518be3b2b5b4e6020c49955f1a7ff3a5c0b001..382de8e085518a6f85efcb3d13250ad8e1edf3cf 100644 (file)
     interactFailureString: petting-failure-firebot
     interactSuccessSound:
       path: /Audio/Ambience/Objects/periodic_beep.ogg
-  - type: Advertise
-    pack: FirebotAd
+  - type: Vocalizer
+  - type: DatasetVocalizer
+    dataset: FirebotAd
 
 - type: entity
   parent: MobSiliconBase
     interactFailureString: petting-failure-medibot
     interactSuccessSound:
       path: /Audio/Ambience/Objects/periodic_beep.ogg
-  - type: Advertise
-    pack: MedibotAds
+  - type: Vocalizer
+  - type: DatasetVocalizer
+    dataset: MedibotAds
   - type: Inventory
     templateId: medibot
   - type: DoAfter
index d57e1c4bf83b6ee1de1f2d17dafbc80abccb38f9..84df160258f9a5959f521488f9dcca0443c9d9e9 100644 (file)
         type: WiresBoundUserInterface
   - type: Computer
     board: SpaceVillainArcadeComputerCircuitboard
-  - type: Advertise
-    pack: SpaceVillainAds
-    minimumWait: 60 # Arcades are noisy
-    maximumWait: 240
+  - type: Vocalizer
+    minVocalizeInterval: 1m # Arcades are noisy
+    maxVocalizeInterval: 4m
+    hideChat: true
+  - type: DatasetVocalizer
+    dataset: SpaceVillainAds
   - type: SpeakOnUIClosed
     pack: SpaceVillainGoodbyes
 
         type: WiresBoundUserInterface
   - type: Computer
     board: BlockGameArcadeComputerCircuitboard
-  - type: Advertise
-    pack: BlockGameAds
-    minimumWait: 60 # Arcades are noisy
-    maximumWait: 240
+  - type: Vocalizer
+    minVocalizeInterval: 1m # Arcades are noisy
+    maxVocalizeInterval: 4m
+    hideChat: true
+  - type: DatasetVocalizer
+    dataset: BlockGameAds
   - type: SpeakOnUIClosed
     pack: BlockGameGoodbyes
index 770a17a8ff7c9b20bb1fcb05d13806afe4e2595f..c4b26e043baf54cb6971e66aae576b5a5456e024 100644 (file)
   - type: Appearance
   - type: Speech
     speechVerb: Robotic
-  - type: Advertise
-    pack: FatExtractorFacts
+  - type: Vocalizer
+    hideChat: true
+  - type: VocalizerRequiresPower
+  - type: DatasetVocalizer
+    dataset: FatExtractorFacts
   - type: StaticPrice
     price: 1000
   - type: ResistLocker
index 748b3426c4e4eb7f5c2ad5ff9c959836d3d7de24..8ea6fee938e2be7ae61b321b70fe3e20bc3b2375 100644 (file)
@@ -5,8 +5,11 @@
   description: A refrigerated storage unit for keeping items cold and fresh.
   components:
   - type: StationAiWhitelist
-  - type: Advertise
-    pack: SmartFridgeAds
+  - type: Vocalizer
+    hideChat: true
+  - type: VocalizerRequiresPower
+  - type: DatasetVocalizer
+    dataset: SmartFridgeAds
   - type: Speech
   - type: Appearance
   - type: Sprite
index 54cb396fdc13b114b49c6009315260e1e89a2cb2..12ce780ff7c712fa11abf07456e919a74499b3a7 100644 (file)
   - type: Speech
     speechVerb: Robotic
     speechSounds: Vending
+  - type: Vocalizer
+    minVocalizeInterval: 8m
+    maxVocalizeInterval: 10m
+    hideChat: true
+  - type: VocalizerRequiresPower
   - type: SpookySpeaker
     messageSet: SpookySpeakerMessagesGeneric
     speakChance: 0.2
         layer:
         - MachineLayer
         density: 190
-  - type: Advertise
-    pack: CondimentVendAds
+  - type: DatasetVocalizer
+    dataset: CondimentVendAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Transform
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: AmmoVendAds
+  - type: DatasetVocalizer
+    dataset: CondimentVendAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     denyState: deny-unshaded
     loopDeny: false
-  - type: Advertise
-    pack: BoozeOMatAds
+  - type: DatasetVocalizer
+    dataset: BoozeOMatAds
   - type: SpeakOnUIClosed
     pack: BoozeOMatGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     denyState: deny-unshaded
     loopDeny: false
-  - type: Advertise
-    pack: BruiseOMatAds
+  - type: DatasetVocalizer
+    dataset: BruiseOMatAds
   - type: SpeakOnUIClosed
     pack: BruiseOMatGoodbyes
   - type: Sprite
     brokenState: broken
     normalState: normal-unshaded
     ejectState: eject-unshaded
-  - type: Advertise
-    pack: ChefvendAds
+  - type: DatasetVocalizer
+    dataset: ChefvendAds
   - type: SpeakOnUIClosed
     pack: ChefvendGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     ejectState: eject-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: CigaretteMachineAds
+  - type: DatasetVocalizer
+    dataset: CigaretteMachineAds
   - type: SpeakOnUIClosed
     pack: CigaretteMachineGoodbyes
   - type: Sprite
     brokenState: broken
     normalState: normal-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: ClothesMateAds
+  - type: DatasetVocalizer
+    dataset: ClothesMateAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     brokenState: broken
     normalState: normal-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: ClothesMateAds
+  - type: DatasetVocalizer
+    dataset: ClothesMateAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     ejectDelay: 5
     soundVend: /Audio/Machines/machine_vend_hot_drink.ogg
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: HotDrinksMachineAds
+  - type: DatasetVocalizer
+    dataset: HotDrinksMachineAds
   - type: SpeakOnUIClosed
     pack: HotDrinksMachineGoodbyes
   - type: Sprite
     denyState: deny-unshaded
     ejectDelay: 1.9
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: RobustSoftdrinksAds
+  - type: DatasetVocalizer
+    dataset: RobustSoftdrinksAds
   - type: SpeakOnUIClosed
     pack: RobustSoftdrinksGoodbyes
   - type: Sprite
     denyState: deny-unshaded
     ejectDelay: 1.9
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: RobustSoftdrinksAds
+  - type: DatasetVocalizer
+    dataset: RobustSoftdrinksAds
   - type: Sprite
     sprite: Structures/Machines/VendingMachines/shamblersjuice.rsi
     layers:
     denyState: deny-unshaded
     ejectDelay: 1.9
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: RobustSoftdrinksAds
+  - type: DatasetVocalizer
+    dataset: RobustSoftdrinksAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     denyState: deny-unshaded
     ejectDelay: 1.9
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: DrGibbAds
+  - type: DatasetVocalizer
+    dataset: DrGibbAds
   - type: SpeakOnUIClosed
     pack: DrGibbGoodbyes
   - type: Sprite
     denyState: deny-unshaded
     ejectDelay: 1.9
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: SmiteAds
+  - type: DatasetVocalizer
+    dataset: SmiteAds
   - type: SpeakOnUIClosed
     pack: SmiteGoodbyes
   - type: PointLight
     brokenState: broken
     normalState: normal-unshaded
     ejectState: eject-unshaded
-  - type: Advertise
-    pack: DinnerwareAds
+  - type: DatasetVocalizer
+    dataset: DinnerwareAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: MagiVendAds
+  - type: DatasetVocalizer
+    dataset: MagiVendAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     brokenState: broken
     normalState: normal-unshaded
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: DiscountDansAds
+  - type: DatasetVocalizer
+    dataset: DiscountDansAds
   - type: SpeakOnUIClosed
     pack: DiscountDansGoodbyes
   - type: Sprite
     ejectState: eject-unshaded
     denyState: deny-unshaded
     ejectDelay: 0.6
-  - type: Advertise
-    pack: NanoMedAds
+  - type: DatasetVocalizer
+    dataset: NanoMedAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     ejectState: eject-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: NutriMaxAds
+  - type: DatasetVocalizer
+    dataset: NutriMaxAds
   - type: SpeakOnUIClosed
     pack: NutriMaxGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     ejectState: eject-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: SecTechAds
+  - type: DatasetVocalizer
+    dataset: SecTechAds
   - type: SpeakOnUIClosed
     pack: SecTechGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     ejectState: eject-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: MegaSeedAds
+  - type: DatasetVocalizer
+    dataset: MegaSeedAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     ejectState: eject-unshaded
     denyState: deny-unshaded
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: GetmoreChocolateCorpAds
+  - type: DatasetVocalizer
+    dataset: GetmoreChocolateCorpAds
   - type: SpeakOnUIClosed
     pack: GetmoreChocolateCorpGoodbyes
   - type: Sprite
     ejectState: eject-unshaded
     denyState: deny-unshaded
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: BodaAds
+  - type: DatasetVocalizer
+    dataset: BodaAds
   - type: SpeakOnUIClosed
     pack: BodaGoodbyes
   - type: Sprite
     ejectState: eject-unshaded
     denyState: deny-unshaded
     screenState: screen
-  - type: Advertise
-    pack: AutoDrobeAds
+  - type: DatasetVocalizer
+    dataset: AutoDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     ejectState: eject-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: VendomatAds
+  - type: DatasetVocalizer
+    dataset: VendomatAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     ejectState: eject-unshaded
     denyState: deny-unshaded
-  - type: Advertise
-    pack: VendomatAds
+  - type: DatasetVocalizer
+    dataset: VendomatAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     normalState: normal-unshaded
     ejectState: eject-unshaded
     ejectDelay: 1.8
-  - type: Advertise
-    pack: GoodCleanFunAds
+  - type: DatasetVocalizer
+    dataset: GoodCleanFunAds
   - type: SpeakOnUIClosed
     pack: GoodCleanFunGoodbyes
   - type: Sprite
     brokenState: broken
     normalState: normal-unshaded
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: ChangAds
+  - type: DatasetVocalizer
+    dataset: ChangAds
   - type: SpeakOnUIClosed
     pack: ChangGoodbyes
   - type: Sprite
     brokenState: broken
     normalState: normal-unshaded
     initialStockQuality: 0.33
-  - type: Advertise
-    pack: DonutAds
+  - type: DatasetVocalizer
+    dataset: DonutAds
   - type: SpeakOnUIClosed
     pack: DonutGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: HyDrobeAds
+  - type: DatasetVocalizer
+    dataset: HyDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: LawDrobeAds
+  - type: DatasetVocalizer
+    dataset: LawDrobeAds
   - type: SpeakOnUIClosed
     pack: LawDrobeGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: SecDrobeAds
+  - type: DatasetVocalizer
+    dataset: SecDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: BarDrobeAds
+  - type: DatasetVocalizer
+    dataset: BarDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: CargoDrobeAds
+  - type: DatasetVocalizer
+    dataset: CargoDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: MediDrobeAds
+  - type: DatasetVocalizer
+    dataset: MediDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: ChemDrobeAds
+  - type: DatasetVocalizer
+    dataset: ChemDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: CuraDrobeAds
+  - type: DatasetVocalizer
+    dataset: CuraDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: AtmosDrobeAds
+  - type: DatasetVocalizer
+    dataset: AtmosDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: EngiDrobeAds
+  - type: DatasetVocalizer
+    dataset: EngiDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: ChefDrobeAds
+  - type: DatasetVocalizer
+    dataset: ChefDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: DetDrobeAds
+  - type: DatasetVocalizer
+    dataset: DetDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: JaniDrobeAds
+  - type: DatasetVocalizer
+    dataset: JaniDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: SciDrobeAds
+  - type: DatasetVocalizer
+    dataset: SciDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: SyndieDrobeAds
+  - type: DatasetVocalizer
+    dataset: SyndieDrobeAds
   - type: SpeakOnUIClosed
     pack: SyndieDrobeGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: RoboDrobeAds
+  - type: DatasetVocalizer
+    dataset: RoboDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: GeneDrobeAds
+  - type: DatasetVocalizer
+    dataset: GeneDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: ViroDrobeAds
+  - type: DatasetVocalizer
+    dataset: ViroDrobeAds
   - type: SpeakOnUIClosed
     pack: GenericVendGoodbyes
   - type: Sprite
     radius: 1.5
     energy: 1.6
     color: "#3c5eb5"
-  - type: Advertise
-    pack: HappyHonkAds
+  - type: DatasetVocalizer
+    dataset: HappyHonkAds
   - type: SpeakOnUIClosed
     pack: HappyHonkGoodbyes
   - type: AccessReader
     offState: off
     brokenState: broken
     normalState: normal-unshaded
-  - type: Advertise
-    pack: PrideDrobeAds
+  - type: DatasetVocalizer
+    dataset: PrideDrobeAds
   - type: SpeakOnUIClosed
     pack: PrideDrobeGoodbyes
   - type: Speech