diff --git a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs new file mode 100644 index 0000000000..6e881aa3d1 --- /dev/null +++ b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs @@ -0,0 +1,148 @@ +using Content.Server.Anomaly.Components; +using Content.Server.DeviceLinking.Systems; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Anomaly.Components; +using Content.Shared.Interaction; +using Content.Shared.Popups; + +namespace Content.Server.Anomaly; + +/// +/// a device that allows you to translate anomaly activity into multitool signals. +/// +public sealed partial class AnomalySynchronizerSystem : EntitySystem +{ + [Dependency] private readonly AnomalySystem _anomaly = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly EntityLookupSystem _entityLookup = default!; + [Dependency] private readonly DeviceLinkSystem _signal = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly PowerReceiverSystem _power = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnPowerChanged); + + SubscribeLocalEvent(OnAnomalyPulse); + SubscribeLocalEvent(OnAnomalySeverityChanged); + SubscribeLocalEvent(OnAnomalyStabilityChanged); + } + + private void OnPowerChanged(EntityUid uid, AnomalySynchronizerComponent component, ref PowerChangedEvent args) + { + if (args.Powered) + return; + + if (!TryComp(component.ConnectedAnomaly, out var anomaly)) + return; + + _anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly); + DisconneсtFromAnomaly(uid, component, anomaly); + } + + private void OnInteractHand(EntityUid uid, AnomalySynchronizerComponent component, InteractHandEvent args) + { + if (!_power.IsPowered(uid)) + return; + + foreach (var entity in _entityLookup.GetEntitiesInRange(uid, 0.15f)) //is the radius of one tile. It must not be set higher, otherwise the anomaly can be moved from tile to tile + { + if (!TryComp(entity, out var anomaly)) + continue; + + + ConnectToAnomaly(uid, component, entity, anomaly); + break; + } + } + + private void ConnectToAnomaly(EntityUid uid, AnomalySynchronizerComponent component, EntityUid auid, AnomalyComponent anomaly) + { + if (component.ConnectedAnomaly == auid) + return; + + component.ConnectedAnomaly = auid; + //move the anomaly to the center of the synchronizer, for aesthetics. + var targetXform = _transform.GetWorldPosition(uid); + _transform.SetWorldPosition(auid, targetXform); + + _anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly); + _popup.PopupEntity(Loc.GetString("anomaly-sync-connected"), uid, PopupType.Medium); + _audio.PlayPvs(component.ConnectedSound, uid); + } + + //TO DO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer. + //Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer. + private void DisconneсtFromAnomaly(EntityUid uid, AnomalySynchronizerComponent component, AnomalyComponent anomaly) + { + if (component.ConnectedAnomaly == null) + return; + + _anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly); + _popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), uid, PopupType.Large); + _audio.PlayPvs(component.ConnectedSound, uid); + + component.ConnectedAnomaly = default!; + } + + private void OnAnomalyPulse(ref AnomalyPulseEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var ent, out var component)) + { + if (args.Anomaly != component.ConnectedAnomaly) + continue; + if (!_power.IsPowered(ent)) + continue; + + _signal.InvokePort(ent, component.PulsePort); + } + } + + private void OnAnomalySeverityChanged(ref AnomalySeverityChangedEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var ent, out var component)) + { + if (args.Anomaly != component.ConnectedAnomaly) + continue; + if (!_power.IsPowered(ent)) + continue; + //The superscritical port is invoked not at the AnomalySupercriticalEvent, + //but at the moment the growth animation starts. Otherwise, there is no point in this port. + //ATTENTION! the console command supercriticalanomaly does not work here, + //as it forcefully causes growth to start without increasing severity. + if (args.Severity >= 1) + _signal.InvokePort(ent, component.SupercritPort); + } + } + private void OnAnomalyStabilityChanged(ref AnomalyStabilityChangedEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var ent, out var component)) + { + if (args.Anomaly != component.ConnectedAnomaly) + continue; + if (TryComp(ent, out var apcPower) && !apcPower.Powered) + continue; + + if (args.Stability < 0.25f) //I couldn't find where these values are stored, so I hardcoded them. Tell me where these variables are stored and I'll fix it + { + _signal.InvokePort(ent, component.DecayingPort); + } + else if (args.Stability > 0.5f) //I couldn't find where these values are stored, so I hardcoded them. Tell me where these variables are stored and I'll fix it + { + _signal.InvokePort(ent, component.GrowingPort); + } + else + { + _signal.InvokePort(ent, component.StabilizePort); + } + } + } +} diff --git a/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs new file mode 100644 index 0000000000..295ce885c2 --- /dev/null +++ b/Content.Server/Anomaly/Components/AnomalySynchronizerComponent.cs @@ -0,0 +1,42 @@ +using Content.Shared.Anomaly; +using Content.Shared.Anomaly.Components; +using Content.Shared.DeviceLinking; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Server.Anomaly.Components; + +/// +/// a device that allows you to translate anomaly activity into multitool signals. +/// +[RegisterComponent, Access(typeof(AnomalySynchronizerSystem))] +public sealed partial class AnomalySynchronizerComponent : Component +{ + /// + /// The uid of the anomaly to which the synchronizer is connected. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityUid? ConnectedAnomaly; + + + [DataField] + public ProtoId DecayingPort = "Decaying"; + + [DataField] + public ProtoId StabilizePort = "Stabilize"; + + [DataField] + public ProtoId GrowingPort = "Growing"; + + [DataField] + public ProtoId PulsePort = "Pulse"; + + [DataField] + public ProtoId SupercritPort = "Supercritical"; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier ConnectedSound = new SoundPathSpecifier("/Audio/Machines/anomaly_sync_connect.ogg"); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier DisconnectedSound = new SoundPathSpecifier("/Audio/Machines/anomaly_sync_connect.ogg"); +} diff --git a/Content.Shared/Anomaly/Components/AnomalyComponent.cs b/Content.Shared/Anomaly/Components/AnomalyComponent.cs index cf3ba75aaa..a6f4f6c086 100644 --- a/Content.Shared/Anomaly/Components/AnomalyComponent.cs +++ b/Content.Shared/Anomaly/Components/AnomalyComponent.cs @@ -243,16 +243,17 @@ public sealed partial class AnomalyComponent : Component /// /// Event raised at regular intervals on an anomaly to do whatever its effect is. /// +/// The anomaly pulsing /// /// [ByRefEvent] -public readonly record struct AnomalyPulseEvent(float Stability, float Severity); +public readonly record struct AnomalyPulseEvent(EntityUid Anomaly, float Stability, float Severity); /// /// Event raised on an anomaly when it reaches a supercritical point. /// [ByRefEvent] -public readonly record struct AnomalySupercriticalEvent; +public readonly record struct AnomalySupercriticalEvent(EntityUid Anomaly); /// /// Event broadcast after an anomaly goes supercritical diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index ca77544a15..48413ac001 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -109,9 +109,9 @@ public abstract class SharedAnomalySystem : EntitySystem var pulse = EnsureComp(uid); pulse.EndTime = Timing.CurTime + pulse.PulseDuration; Appearance.SetData(uid, AnomalyVisuals.IsPulsing, true); - - var ev = new AnomalyPulseEvent(component.Stability, component.Severity); - RaiseLocalEvent(uid, ref ev); + + var ev = new AnomalyPulseEvent(uid, component.Stability, component.Severity); + RaiseLocalEvent(uid, ref ev, true); } /// @@ -154,8 +154,8 @@ public abstract class SharedAnomalySystem : EntitySystem if (_net.IsServer) _sawmill.Info($"Raising supercritical event. Entity: {ToPrettyString(uid)}"); - var ev = new AnomalySupercriticalEvent(); - RaiseLocalEvent(uid, ref ev); + var ev = new AnomalySupercriticalEvent(uid); + RaiseLocalEvent(uid, ref ev, true); EndAnomaly(uid, component, true); } diff --git a/Resources/Audio/Machines/anomaly_sync_connect.ogg b/Resources/Audio/Machines/anomaly_sync_connect.ogg new file mode 100644 index 0000000000..c718eaf459 Binary files /dev/null and b/Resources/Audio/Machines/anomaly_sync_connect.ogg differ diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml index bbf4ea9ac0..3fa3aa06d7 100644 --- a/Resources/Audio/Machines/attributions.yml +++ b/Resources/Audio/Machines/attributions.yml @@ -46,4 +46,9 @@ - files: ["warning_buzzer.ogg"] license: "CC-BY-SA-3.0" copyright: "Taken from TG station." - source: "https://github.com/tgstation/tgstation/blob/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0/sound/machines/warning-buzzer.ogg" \ No newline at end of file + source: "https://github.com/tgstation/tgstation/blob/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0/sound/machines/warning-buzzer.ogg" + +- files: ["anomaly_sync_connect.ogg"] + license: "CC0-1.0" + copyright: Created by newagesoup, convert to ogg mono by TheShuEd" + source: "https://freesound.org/people/newagesoup/sounds/341172/" \ No newline at end of file diff --git a/Resources/Locale/en-US/anomaly/anomaly.ftl b/Resources/Locale/en-US/anomaly/anomaly.ftl index 29d5169694..83b992ce23 100644 --- a/Resources/Locale/en-US/anomaly/anomaly.ftl +++ b/Resources/Locale/en-US/anomaly/anomaly.ftl @@ -25,6 +25,9 @@ anomaly-scanner-particle-unstable = - [color=plum]Unstable type:[/color] {$type} anomaly-scanner-particle-containment = - [color=goldenrod]Containment type:[/color] {$type} anomaly-scanner-pulse-timer = Time until next pulse: [color=gray]{$time}[/color] +anomaly-sync-connected = Anomaly successfully attached +anomaly-sync-disconnected = The connection to the anomaly has been lost! + anomaly-generator-ui-title = Anomaly Generator anomaly-generator-fuel-display = Fuel: anomaly-generator-cooldown = Cooldown: [color=gray]{$time}[/color] diff --git a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl index 154f20eee7..e5f92c5b00 100644 --- a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl @@ -45,3 +45,18 @@ signal-port-description-air-warning = This port is invoked with HIGH when in war signal-port-name-air-normal = Normal signal-port-description-air-normal = This port is invoked with HIGH when in normal mode and LOW when not. + +signal-port-name-decaying = Decaying +signal-port-description-decaying = This port is invoked when a bound anomaly starts to decay. + +signal-port-name-stabilize = Stabilize +signal-port-description-stabilize = This port is invoked when a bound anomaly is normalized. + +signal-port-name-growing = Growing +signal-port-description-growing = This port is invoked when a bound anomaly starts to grow. + +signal-port-name-pulse = Pulse +signal-port-description-pulse = This port is invoked when a bound anomaly is pulsing. + +signal-port-name-supercrit = Supercritical +signal-port-description-supercrit = This port is invoked when a bound anomaly explode after supercrit state. \ No newline at end of file diff --git a/Resources/Locale/en-US/research/technologies.ftl b/Resources/Locale/en-US/research/technologies.ftl index 28c7377082..0f8a657aa4 100644 --- a/Resources/Locale/en-US/research/technologies.ftl +++ b/Resources/Locale/en-US/research/technologies.ftl @@ -53,7 +53,7 @@ research-technology-advanced-parts = Advanced Parts research-technology-grappling = Grappling research-technology-abnormal-artifact-manipulation = Abnormal Artifact Manipulation research-technology-gravity-manipulation = Gravity Manipulation -research-technology-mobile-anomaly-tech = Mobile Anomaly Tech +research-technology-advanced-anomaly-research = Advanced Anomaly Research research-technology-rped = Rapid Part Exchange research-technology-super-parts = Super Parts diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml index 73e96978a7..6e97c3c809 100644 --- a/Resources/Prototypes/DeviceLinking/source_ports.yml +++ b/Resources/Prototypes/DeviceLinking/source_ports.yml @@ -114,3 +114,28 @@ name: signal-port-name-air-normal description: signal-port-description-air-normal defaultLinks: [ DoorBolt ] + +- type: sourcePort + id: Decaying + name: signal-port-name-decaying + description: signal-port-description-decaying + +- type: sourcePort + id: Stabilize + name: signal-port-name-stabilize + description: signal-port-description-stabilize + +- type: sourcePort + id: Growing + name: signal-port-name-growing + description: signal-port-description-growing + +- type: sourcePort + id: Pulse + name: signal-port-name-pulse + description: signal-port-description-pulse + +- type: sourcePort + id: Supercritical + name: signal-port-name-supercrit + description: signal-port-description-supercrit \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml index 043bf707b4..3b5d33ee06 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml @@ -19,3 +19,4 @@ - AnomalyRock - AnomalyLiquid chance: 1 + offset: 0.15 # not to put it higher. The anomaly sychnronizer looks for anomalies within this radius, and if the radius is higher, the anomaly can be attracted from a neighboring tile. diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index a25e5b7069..2aed92bc28 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -233,6 +233,22 @@ Cable: 1 PlasmaGlass: 10 +- type: entity + parent: BaseMachineCircuitboard + id: AnomalySynchronizerCircuitboard + name: anomaly synchronizer machine board + description: A machine printed circuit board for an anomaly synchronizer. + components: + - type: Sprite + state: science + - type: MachineBoard + prototype: MachineAnomalySynchronizer + requirements: + Manipulator: 2 + Capacitor: 5 + materialRequirements: + PlasmaGlass: 25 + - type: entity parent: BaseMachineCircuitboard id: APECircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml new file mode 100644 index 0000000000..d37cc714e2 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml @@ -0,0 +1,75 @@ +- type: entity + id: MachineAnomalySynchronizer + parent: [ BaseMachinePowered, ConstructibleMachine ] + name: anomaly synchronizer + description: A sophisticated device that reads changes in anomalous waves, and converts them into energy signals. + components: + - type: AnomalySynchronizer + - type: Machine + board: AnomalySynchronizerCircuitboard + - type: DeviceNetwork + deviceNetId: Wireless + - type: WirelessNetworkConnection + range: 300 + - type: DeviceNetworkRequiresPower + - type: DeviceLinkSource + ports: + - Decaying + - Stabilize + - Growing + - Pulse + - Supercritical + - type: Sprite + noRot: true + sprite: Structures/Machines/anomaly_sync.rsi + layers: + - state: base + - state: energy + shader: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + - type: AmbientSound + enabled: false + sound: + path: /Audio/Machines/scan_loop.ogg + range: 5 + volume: -8 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.35,-0.35,0.35,0.35" + density: 190 + mask: + - MachineMask + layer: + - Impassable + - MidImpassable + - LowImpassable + hard: False + - type: Transform + anchored: true + noRot: false + - type: ApcPowerReceiver + powerLoad: 15000 + needsPower: true + - type: UpgradePowerDraw + powerDrawMultiplier: 0.80 + scaling: Exponential + - type: ItemPlacer + whitelist: + components: + - Anomaly + - type: DeviceList + - type: PointLight + radius: 1.8 + energy: 1.6 + color: "#b53ca1" + - type: LitOnPowered + - type: Appearance + - type: GenericVisualizer + visuals: + enum.PowerDeviceVisuals.Powered: + enum.PowerDeviceVisualLayers.Powered: + True: { visible: true } + False: { visible: false } diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index fb77167b73..b82f3f6828 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -366,6 +366,7 @@ - AnalysisComputerCircuitboard - ExosuitFabricatorMachineCircuitboard - AnomalyVesselCircuitboard + - AnomalySynchronizerCircuitboard - APECircuitboard - ArtifactAnalyzerMachineCircuitboard - TraversalDistorterMachineCircuitboard diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index bc54c78ca1..88b655644f 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -248,6 +248,16 @@ Steel: 100 Glass: 900 +- type: latheRecipe + id: AnomalySynchronizerCircuitboard + result: AnomalySynchronizerCircuitboard + completetime: 4 + materials: + Steel: 500 + Glass: 700 + Gold: 200 + Silver: 100 + - type: latheRecipe id: APECircuitboard result: APECircuitboard diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml index 02d66c06bf..363dee815e 100644 --- a/Resources/Prototypes/Research/experimental.yml +++ b/Resources/Prototypes/Research/experimental.yml @@ -99,16 +99,17 @@ - TraversalDistorterMachineCircuitboard - type: technology - id: MobileAnomalyTech - name: research-technology-mobile-anomaly-tech + id: AdvancedAnomalyResearch + name: research-technology-advanced-anomaly-research icon: - sprite: Objects/Weapons/Guns/Revolvers/chimp.rsi + sprite: Structures/Machines/anomaly_sync.rsi state: base discipline: Experimental tier: 2 cost: 10000 recipeUnlocks: - WeaponPistolCHIMP + - AnomalySynchronizerCircuitboard technologyPrerequisites: - BasicAnomalousResearch diff --git a/Resources/Textures/Structures/Machines/anomaly_sync.rsi/base.png b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/base.png new file mode 100644 index 0000000000..e51f0e4a80 Binary files /dev/null and b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/base.png differ diff --git a/Resources/Textures/Structures/Machines/anomaly_sync.rsi/energy.png b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/energy.png new file mode 100644 index 0000000000..81e8758c05 Binary files /dev/null and b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/energy.png differ diff --git a/Resources/Textures/Structures/Machines/anomaly_sync.rsi/meta.json b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/meta.json new file mode 100644 index 0000000000..1a8f1a82ec --- /dev/null +++ b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Created by ThrShuEd (github) for Space Station 14", + "size": { + "x": 48, + "y": 32 + }, + "states": [ + { + "name": "base" + }, + { + "name": "energy", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "pulse", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Machines/anomaly_sync.rsi/pulse.png b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/pulse.png new file mode 100644 index 0000000000..cceb35afcb Binary files /dev/null and b/Resources/Textures/Structures/Machines/anomaly_sync.rsi/pulse.png differ