var fuelCompletion = Math.Clamp((float) state.FuelAmount / state.FuelCost, 0f, 1f);
FuelBar.Value = fuelCompletion;
- FuelText.Text = $"{fuelCompletion:P}";
+
+ var charges = state.FuelAmount / state.FuelCost;
+ FuelText.Text = Loc.GetString("anomaly-generator-charges", ("charges", charges));
UpdateTimer();
UpdateReady(); // yes this can trigger twice. no i don't care
-using Content.Server.Anomaly.Components;
+using Content.Server.Anomaly.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Anomaly;
using Content.Shared.CCVar;
using Content.Shared.Materials;
+using Content.Shared.Radio;
+using Robust.Shared.Audio;
using Content.Shared.Physics;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
SubscribeLocalEvent<AnomalyGeneratorComponent, MaterialAmountChangedEvent>(OnGeneratorMaterialAmountChanged);
SubscribeLocalEvent<AnomalyGeneratorComponent, AnomalyGeneratorGenerateButtonPressedEvent>(OnGenerateButtonPressed);
SubscribeLocalEvent<AnomalyGeneratorComponent, PowerChangedEvent>(OnGeneratorPowerChanged);
+ SubscribeLocalEvent<AnomalyGeneratorComponent, EntityUnpausedEvent>(OnGeneratorUnpaused);
+ SubscribeLocalEvent<GeneratingAnomalyGeneratorComponent, ComponentStartup>(OnGeneratingStartup);
+ SubscribeLocalEvent<GeneratingAnomalyGeneratorComponent, EntityUnpausedEvent>(OnGeneratingUnpaused);
}
private void OnGeneratorPowerChanged(EntityUid uid, AnomalyGeneratorComponent component, ref PowerChangedEvent args)
TryGeneratorCreateAnomaly(uid, component);
}
+ private void OnGeneratorUnpaused(EntityUid uid, AnomalyGeneratorComponent component, ref EntityUnpausedEvent args)
+ {
+ component.CooldownEndTime += args.PausedTime;
+ }
+
public void UpdateGeneratorUi(EntityUid uid, AnomalyGeneratorComponent component)
{
var materialAmount = _material.GetMaterialAmount(uid, component.RequiredMaterial);
if (Timing.CurTime < component.CooldownEndTime)
return;
- var grid = Transform(uid).GridUid;
- if (grid == null)
- return;
-
if (!_material.TryChangeMaterialAmount(uid, component.RequiredMaterial, -component.MaterialPerAnomaly))
return;
- SpawnOnRandomGridLocation(grid.Value, component.SpawnerPrototype);
+ var generating = EnsureComp<GeneratingAnomalyGeneratorComponent>(uid);
+ generating.EndTime = Timing.CurTime + component.GenerationLength;
+ generating.AudioStream = Audio.PlayPvs(component.GeneratingSound, uid, AudioParams.Default.WithLoop(true));
component.CooldownEndTime = Timing.CurTime + component.CooldownLength;
UpdateGeneratorUi(uid, component);
}
Spawn(toSpawn, targetCoords);
}
+
+ private void OnGeneratingStartup(EntityUid uid, GeneratingAnomalyGeneratorComponent component, ComponentStartup args)
+ {
+ Appearance.SetData(uid, AnomalyGeneratorVisuals.Generating, true);
+ }
+
+ private void OnGeneratingUnpaused(EntityUid uid, GeneratingAnomalyGeneratorComponent component, ref EntityUnpausedEvent args)
+ {
+ component.EndTime += args.PausedTime;
+ }
+
+ private void OnGeneratingFinished(EntityUid uid, AnomalyGeneratorComponent component)
+ {
+ var grid = Transform(uid).GridUid;
+ if (grid == null)
+ return;
+
+ SpawnOnRandomGridLocation(grid.Value, component.SpawnerPrototype);
+ RemComp<GeneratingAnomalyGeneratorComponent>(uid);
+ Appearance.SetData(uid, AnomalyGeneratorVisuals.Generating, false);
+ Audio.PlayPvs(component.GeneratingFinishedSound, uid);
+
+ var message = Loc.GetString("anomaly-generator-announcement");
+ _radio.SendRadioMessage(uid, message, _prototype.Index<RadioChannelPrototype>(component.ScienceChannel));
+ }
+
+ private void UpdateGenerator()
+ {
+ foreach (var (active, gen) in EntityQuery<GeneratingAnomalyGeneratorComponent, AnomalyGeneratorComponent>())
+ {
+ var ent = active.Owner;
+
+ if (Timing.CurTime < active.EndTime)
+ continue;
+ active.AudioStream?.Stop();
+ OnGeneratingFinished(ent, gen);
+ }
+ }
}
using Content.Server.DoAfter;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Materials;
+using Content.Server.Radio.EntitySystems;
using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Physics.Events;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+
namespace Content.Server.Anomaly;
/// <summary>
public sealed partial class AnomalySystem : SharedAnomalySystem
{
[Dependency] private readonly IConfigurationManager _configuration = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly AmbientSoundSystem _ambient = default!;
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!;
[Dependency] private readonly MaterialStorageSystem _material = default!;
+ [Dependency] private readonly RadioSystem _radio = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public const float MinParticleVariation = 0.8f;
{
base.Update(frameTime);
+ UpdateGenerator();
UpdateVessels();
}
}
using Content.Shared.Materials;
+using Content.Shared.Radio;
+using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
[DataField("cooldownLength"), ViewVariables(VVAccess.ReadWrite)]
public TimeSpan CooldownLength = TimeSpan.FromMinutes(5);
+ /// <summary>
+ /// How long it takes to generate an anomaly after pushing the button.
+ /// </summary>
+ [DataField("generationLength"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan GenerationLength = TimeSpan.FromSeconds(8);
+
/// <summary>
/// The material needed to generate an anomaly
/// </summary>
/// </summary>
[DataField("spawnerPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string SpawnerPrototype = "RandomAnomalySpawner";
+
+ /// <summary>
+ /// The radio channel for science
+ /// </summary>
+ [DataField("scienceChannel", customTypeSerializer: typeof(PrototypeIdSerializer<RadioChannelPrototype>))]
+ public string ScienceChannel = "Science";
+
+ /// <summary>
+ /// The sound looped while an anomaly generates
+ /// </summary>
+ [DataField("generatingSound")]
+ public SoundSpecifier? GeneratingSound;
+
+ /// <summary>
+ /// Sound played on generation completion.
+ /// </summary>
+ [DataField("generatingFinishedSound")]
+ public SoundSpecifier? GeneratingFinishedSound;
}
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Anomaly.Components;
+
+[RegisterComponent]
+public sealed class GeneratingAnomalyGeneratorComponent : Component
+{
+ /// <summary>
+ /// When the generating period will end.
+ /// </summary>
+ [DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
+ public TimeSpan EndTime = TimeSpan.Zero;
+
+ public IPlayingAudioStream? AudioStream;
+}
Base
}
+[Serializable, NetSerializable]
+public enum AnomalyGeneratorVisuals : byte
+{
+ Generating
+}
+
+[Serializable, NetSerializable]
+public enum AnomalyGeneratorVisualLayers : byte
+{
+ Base
+}
+
[Serializable, NetSerializable]
public enum AnomalyScannerUiKey : byte
{
+- files: ["anomaly_generate.ogg"]
+ license: "CC0-1.0"
+ copyright: "Created by szegvari, converted to mono and .ogg by EmoGarbage404 (github)."
+ source: "https://freesound.org/people/szegvari/sounds/536794/"
+
+- files: ["beep.ogg"]
+ license: "CC0-1.0"
+ copyright: "Created by dotY21."
+ source: "https://freesound.org/people/dotY21/sounds/330726/"
+
- files: ["vending_restock_start.ogg"]
license: "CC0-1.0"
copyright: "https://freesound.org/people/Defaultv/"
anomaly-generator-no-cooldown = Cooldown: [color=gray]Complete[/color]
anomaly-generator-yes-fire = Status: [color=forestgreen]Ready[/color]
anomaly-generator-no-fire = Status: [color=crimson]Not ready[/color]
-anomaly-generator-generate = Generate Anomaly
\ No newline at end of file
+anomaly-generator-generate = Generate Anomaly
+anomaly-generator-charges = {$charges ->
+ [one] {$charges} charge
+ *[other] {$charges} charges
+}
+anomaly-generator-announcement = An anomaly has been generated!
\ No newline at end of file
- state: inserting
visible: false
map: ["enum.MaterialStorageVisualLayers.Inserting"]
+ - state: generating
+ visible: false
+ shader: unshaded
+ map: ["enum.AnomalyGeneratorVisualLayers.Base"]
- type: Transform
anchored: true
- type: ApcPowerReceiver
- type: ExtensionCableReceiver
- type: AmbientSound
range: 5
- volume: -3
+ volume: -6
sound:
path: /Audio/Ambience/Objects/anomaly_generator.ogg
- type: Physics
bodyType: Static
- type: AnomalyGenerator
+ generatingSound:
+ path: /Audio/Machines/anomaly_generate.ogg
+ generatingFinishedSound:
+ path: /Audio/Machines/beep.ogg
- type: MaterialStorage
whitelist:
tags:
- key: enum.AnomalyGeneratorUiKey.Key
type: AnomalyGeneratorBoundUserInterface
- type: Appearance
+ - type: ActiveRadio
+ channels:
+ - Science
- type: GenericVisualizer
visuals:
enum.PowerDeviceVisuals.Powered:
enum.PowerDeviceVisualLayers.Powered:
True: { visible: true }
False: { visible: false }
+ enum.AnomalyGeneratorVisuals.Generating:
+ enum.AnomalyGeneratorVisualLayers.Base:
+ True: { visible: true }
+ False: { visible: false }
- type: WiresVisuals
- type: StaticPrice
price: 5000
"license":"CC0-1.0",
"copyright":"Created by EmoGarbage",
"states":[
+ {
+ "name": "generating",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
{
"name":"base"
},