From 273e0968e4cd5b7dd049cf6fe83669f124b292ef Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 6 Nov 2022 18:05:44 -0500 Subject: [PATCH] XenoArch [Science Overhaul] (#12204) * multi-node xeno artifacts * refactor existing artifact effects * more tweaks to generation * more shit plus fix tests * more generation stuff plus threat levels * doink * now make it build * defer the artifact activation to not cause errors also pricing * some changes * all of the yaml + ui stuff for artifact analyzer * machine linking and starting to make the ui functional * artifact analyzer display * a shit ton of artifact analyzer stuff * more changes; making destroy work properly; progress bar tweaks * getting shit going! ALL RIGHT * small tweaks that didn't help much * Komm susser todd: the end of analysis * recipes and hints and ui, oh my! * add some in-game sources gotta prepare for day 1 launch * node data + ditch random seed in place of id * bunch of triggers * finish off the last few triggers * implement machine examine verb * knock, flicker, blink, throw * shatter, foam, shuffle, heat * fix all the shit i broke * *some* of these have to be good, no? 25 effects * callin' it there for effects * comments + reword some trigger hints * don't mind this little commit here * byref event * fix brokey node entry * fix low pressure trigger * mirror review plus fixing 0x40's bug also the throw artifact threw incorrectly * randomize the event message a teeny bit --- .../Ui/AnalysisConsoleBoundUserInterface.cs | 64 +++ .../Ui/AnalysisConsoleMenu.xaml | 59 +++ .../Ui/AnalysisConsoleMenu.xaml.cs | 157 +++++++ .../Tests/PrototypeSaveTest.cs | 1 - .../ReactionEffects/FoamAreaReactionEffect.cs | 6 +- .../ReagentEffects/ActivateArtifact.cs | 13 + .../Salvage/SalvageMagnetComponent.cs | 10 + Content.Server/Salvage/SalvageSystem.cs | 1 + .../StationEvents/Events/BluespaceArtifact.cs | 46 ++ .../ActiveArtifactAnalyzerComponent.cs | 24 + .../ActiveScannedArtifactComponent.cs | 22 + .../Components/AnalysisConsoleComponent.cs | 31 ++ .../Components/ArtifactAnalyzerComponent.cs | 67 +++ .../SuppressArtifactContainerComponent.cs | 2 +- .../Systems/ArtifactAnalyzerSystem.cs | 413 ++++++++++++++++++ .../SuppressArtifactContainerSystem.cs | 21 +- .../XenoArtifacts/ArtifactComponent.cs | 138 +++++- .../XenoArtifacts/ArtifactSystem.Nodes.cs | 198 +++++++++ .../XenoArtifacts/ArtifactSystem.cs | 180 +++++++- .../DamageNearbyArtifactComponent.cs | 44 ++ .../Components/DiseaseArtifactComponent.cs | 15 +- .../Components/FoamArtifactComponent.cs | 54 +++ .../Components/GasArtifactComponent.cs | 2 +- .../Components/KnockArtifactComponent.cs | 14 + .../LightFlickerArtifactComponent.cs | 20 + .../Components/RadiateArtifactComponent.cs | 17 - .../RandomTeleportArtifactComponent.cs | 15 + .../Components/ShuffleArtifactComponent.cs | 12 + .../Components/SpawnArtifactComponent.cs | 22 +- .../Components/TelepathicArtifactComponent.cs | 4 +- .../TemperatureArtifactComponent.cs | 6 +- .../Components/ThrowArtifactComponent.cs | 27 ++ .../Systems/DamageNearbyArtifactSystem.cs | 36 ++ .../Effects/Systems/DiseaseArtifactSystem.cs | 25 +- .../Effects/Systems/FoamArtifactSystem.cs | 42 ++ .../Effects/Systems/GasArtifactSystem.cs | 15 +- .../Effects/Systems/KnockArtifactSystem.cs | 24 + .../Systems/LightFlickerArtifactSystem.cs | 38 ++ .../Effects/Systems/RadiateArtifactSystem.cs | 20 - .../Systems/RandomTeleportArtifactSystem.cs | 30 ++ .../Effects/Systems/ShuffleArtifactSystem.cs | 43 ++ .../Effects/Systems/SpawnArtifactSystem.cs | 41 +- .../Systems/TelepathicArtifactSystem.cs | 11 +- .../Systems/TemperatureArtifactSystem.cs | 2 +- .../Effects/Systems/ThrowArtifactSystem.cs | 49 +++ ...actActivatedEvent.cs => ArtifactEvents.cs} | 17 + .../Events/RandomizeTriggerEvent.cs | 9 - .../RandomArtifactSpriteSystem.cs | 14 +- .../ArtifactAnchorTriggerComponent.cs | 13 + .../ArtifactDamageTriggerComponent.cs | 29 ++ .../ArtifactDeathTriggerComponent.cs | 14 + .../ArtifactExamineTriggerComponent.cs | 10 + .../Components/ArtifactGasTriggerComponent.cs | 2 +- .../ArtifactMagnetTriggerComponent.cs | 14 + .../ArtifactMusicTriggerComponent.cs | 14 + .../ArtifactPressureTriggerComponent.cs | 20 + .../Systems/ArtifactAnchorTriggerSystem.cs | 22 + .../Systems/ArtifactDamageTriggerSystem.cs | 35 ++ .../Systems/ArtifactDeathTriggerSystem.cs | 40 ++ .../ArtifactElectricityTriggerSystem.cs | 11 +- .../Systems/ArtifactExamineTriggerSystem.cs | 20 + .../Systems/ArtifactGasTriggerSystem.cs | 28 +- .../Systems/ArtifactHeatTriggerSystem.cs | 14 +- .../ArtifactInteractionTriggerSystem.cs | 1 - .../Systems/ArtifactMagnetTriggerSystem.cs | 40 ++ .../Systems/ArtifactMusicTriggerSystem.cs | 46 ++ .../Systems/ArtifactPressureTriggerSystem.cs | 40 ++ .../Systems/ArtifactTimerTriggerSystem.cs | 23 +- .../Equipment/SharedArtifactAnalyzer.cs | 78 ++++ .../XenoArtifacts/ArtifactEffectPrototype.cs | 36 ++ .../XenoArtifacts/ArtifactTriggerPrototype.cs | 25 ++ .../Audio/Effects/Lightning/lightningbolt.ogg | Bin 0 -> 34844 bytes Resources/Audio/Machines/license.txt | 4 + Resources/Audio/Machines/scan_finish.ogg | Bin 0 -> 5149 bytes Resources/Audio/Machines/scan_loop.ogg | Bin 0 -> 39672 bytes .../en-US/machine-linking/receiver_ports.ftl | 6 + .../events/bluespace-artifact.ftl | 9 + .../xenoarchaeology/artifact-analyzer.ftl | 29 ++ .../en-US/xenoarchaeology/artifact-hints.ftl | 27 ++ .../en-US/xenoarchaeology/misc-artifact.ftl | 4 + Resources/Maps/Salvage/medium-pirate.yml | 2 +- Resources/Maps/kettle.yml | 2 +- Resources/Maps/marathon.yml | 2 +- .../Catalog/Cargo/cargo_science.yml | 10 + .../Catalog/Fills/Lockers/heads.yml | 2 + .../Catalog/Research/technologies.yml | 2 + .../Entities/Effects/bluespace_flash.yml | 13 + .../Markers/Spawners/Random/artifacts.yml | 16 +- .../Circuitboards/Machine/production.yml | 16 + .../Devices/Circuitboards/computer.yml | 11 + .../Xenoarchaeology/artifact_equipment.yml | 1 + .../Specific/Xenoarchaeology/artifacts.yml | 154 +------ .../Machines/Computers/computers.yml | 40 ++ .../Structures/Machines/artifact_analyzer.yml | 68 +++ .../Entities/Structures/Machines/lathe.yml | 2 + .../Entities/Structures/Machines/research.yml | 2 +- Resources/Prototypes/GameRules/events.yml | 10 + .../MachineLinking/receiver_ports.yml | 5 + .../MachineLinking/transmitter_ports.yml | 6 + .../Prototypes/Recipes/Lathes/electronics.yml | 19 + .../Prototypes/XenoArch/artifact_effects.yml | 318 ++++++++++++++ .../Prototypes/XenoArch/artifact_triggers.yml | 144 ++++++ .../Machines/artifact_analyzer.rsi/icon.png | Bin 0 -> 308 bytes .../Machines/artifact_analyzer.rsi/meta.json | 17 + .../artifact_analyzer.rsi/unshaded.png | Bin 0 -> 185 bytes .../Machines/computers.rsi/artifact.png | Bin 0 -> 2239 bytes .../Machines/computers.rsi/meta.json | 42 ++ 107 files changed, 3321 insertions(+), 358 deletions(-) create mode 100644 Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs create mode 100644 Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml create mode 100644 Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs create mode 100644 Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs create mode 100644 Content.Server/StationEvents/Events/BluespaceArtifact.cs create mode 100644 Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs create mode 100644 Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs rename Content.Server/Xenoarchaeology/{XenoArtifacts => }/Equipment/Components/SuppressArtifactContainerComponent.cs (71%) create mode 100644 Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs rename Content.Server/Xenoarchaeology/{XenoArtifacts => }/Equipment/Systems/SuppressArtifactContainerSystem.cs (58%) create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs delete mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RadiateArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs delete mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RadiateArtifactSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs rename Content.Server/Xenoarchaeology/XenoArtifacts/Events/{ArtifactActivatedEvent.cs => ArtifactEvents.cs} (52%) delete mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Events/RandomizeTriggerEvent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs create mode 100644 Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs create mode 100644 Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs create mode 100644 Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs create mode 100644 Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs create mode 100644 Resources/Audio/Effects/Lightning/lightningbolt.ogg create mode 100644 Resources/Audio/Machines/scan_finish.ogg create mode 100644 Resources/Audio/Machines/scan_loop.ogg create mode 100644 Resources/Locale/en-US/station-events/events/bluespace-artifact.ftl create mode 100644 Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl create mode 100644 Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl create mode 100644 Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl create mode 100644 Resources/Prototypes/Entities/Effects/bluespace_flash.yml create mode 100644 Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml create mode 100644 Resources/Prototypes/XenoArch/artifact_effects.yml create mode 100644 Resources/Prototypes/XenoArch/artifact_triggers.yml create mode 100644 Resources/Textures/Structures/Machines/artifact_analyzer.rsi/icon.png create mode 100644 Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json create mode 100644 Resources/Textures/Structures/Machines/artifact_analyzer.rsi/unshaded.png create mode 100644 Resources/Textures/Structures/Machines/computers.rsi/artifact.png diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs new file mode 100644 index 0000000000..53c148d347 --- /dev/null +++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs @@ -0,0 +1,64 @@ +using Content.Shared.Xenoarchaeology.Equipment; +using JetBrains.Annotations; +using Robust.Client.GameObjects; + +namespace Content.Client.Xenoarchaeology.Ui; + +[UsedImplicitly] +public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface +{ + private AnalysisConsoleMenu? _consoleMenu; + + public AnalysisConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) + { + + } + + protected override void Open() + { + base.Open(); + + _consoleMenu = new AnalysisConsoleMenu(); + + _consoleMenu.OnClose += Close; + _consoleMenu.OpenCentered(); + + _consoleMenu.OnServerSelectionButtonPressed += _ => + { + SendMessage(new AnalysisConsoleServerSelectionMessage()); + }; + _consoleMenu.OnScanButtonPressed += _ => + { + SendMessage(new AnalysisConsoleScanButtonPressedMessage()); + }; + _consoleMenu.OnDestroyButtonPressed += _ => + { + SendMessage(new AnalysisConsoleDestroyButtonPressedMessage()); + }; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + switch (state) + { + case AnalysisConsoleScanUpdateState msg: + _consoleMenu?.SetDestroyButtonDisabled(msg); + _consoleMenu?.SetScanButtonDisabled(msg); + _consoleMenu?.UpdateInformationDisplay(msg); + _consoleMenu?.UpdateProgressBar(msg); + break; + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + return; + _consoleMenu?.Dispose(); + } +} + diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml new file mode 100644 index 0000000000..fc705306aa --- /dev/null +++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs new file mode 100644 index 0000000000..24c0542f1a --- /dev/null +++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs @@ -0,0 +1,157 @@ +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Content.Shared.Xenoarchaeology.Equipment; +using Content.Shared.Xenoarchaeology.XenoArtifacts; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client.Xenoarchaeology.Ui; + +[GenerateTypedNameReferences] +public sealed partial class AnalysisConsoleMenu : FancyWindow +{ + [Dependency] private readonly IEntityManager _ent = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + + public event Action? OnServerSelectionButtonPressed; + public event Action? OnScanButtonPressed; + public event Action? OnDestroyButtonPressed; + + public AnalysisConsoleMenu() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + ServerSelectionButton.OnPressed += a => OnServerSelectionButtonPressed?.Invoke(a); + ScanButton.OnPressed += a => OnScanButtonPressed?.Invoke(a); + DestroyButton.OnPressed += a => OnDestroyButtonPressed?.Invoke(a); + } + + public void SetScanButtonDisabled(AnalysisConsoleScanUpdateState state) + { + var disabled = !state.CanScan; + + ScanButton.Disabled = disabled; + } + + public void SetDestroyButtonDisabled(AnalysisConsoleScanUpdateState state) + { + var disabled = !state.ServerConnected || !state.CanScan; + + DestroyButton.Disabled = disabled; + + if (disabled) + { + DestroyButton.RemoveStyleClass(StyleBase.ButtonCaution); + } + else + { + DestroyButton.AddStyleClass(StyleBase.ButtonCaution); + } + } + + public void UpdateArtifactIcon(EntityUid? uid) + { + if (uid == null) + { + ArtifactDisplay.Visible = false; + return; + } + ArtifactDisplay.Visible = true; + + if (!_ent.TryGetComponent(uid, out var sprite)) + return; + + ArtifactDisplay.Sprite = sprite; + } + + public void UpdateInformationDisplay(AnalysisConsoleScanUpdateState state) + { + var message = new FormattedMessage(); + + if (state.Scanning) + { + message.AddMarkup(Loc.GetString("analysis-console-info-scanner")); + Information.SetMessage(message); + return; + } + + //do this here + UpdateArtifactIcon(state.Artifact); + + if (state.Artifact == null)//no scan present + { + if (!state.AnalyzerConnected) //no analyzer connected + message.AddMarkup(Loc.GetString("analysis-console-info-no-scanner")); + else if (!state.CanScan) //no artifact + message.AddMarkup(Loc.GetString("analysis-console-info-no-artifact")); + else if (state.Artifact == null) //ready to go + message.AddMarkup(Loc.GetString("analysis-console-info-ready")); + } + + if (state.Id != null) //node id + message.AddMarkup(Loc.GetString("analysis-console-info-id", ("id", state.Id))+"\n"); + if (state.Depth != null) //node depth + message.AddMarkup(Loc.GetString("analysis-console-info-depth", ("depth", state.Depth))+"\n"); + + if (state.Triggered != null) //whether it has been triggered + { + var activated = state.Triggered.Value + ? "analysis-console-info-triggered-true" + : "analysis-console-info-triggered-false"; + message.AddMarkup(Loc.GetString(activated)+"\n"); + } + + message.AddMarkup("\n"); + var needSecondNewline = false; + + if (state.TriggerProto != null && //possible triggers + _proto.TryIndex(state.TriggerProto, out var trigger) && + trigger.TriggerHint != null) + { + message.AddMarkup(Loc.GetString("analysis-console-info-trigger", + ("trigger", Loc.GetString(trigger.TriggerHint))) + "\n"); + needSecondNewline = true; + } + + if (state.EffectProto != null && //possible effects + _proto.TryIndex(state.EffectProto, out var effect) && + effect.EffectHint != null) + { + message.AddMarkup(Loc.GetString("analysis-console-info-effect", + ("effect", Loc.GetString(effect.EffectHint))) + "\n"); + needSecondNewline = true; + } + + if (needSecondNewline) + message.AddMarkup("\n"); + + if (state.Edges != null) //number of edges + message.AddMarkup(Loc.GetString("analysis-console-info-edges", ("edges", state.Edges))+"\n"); + if (state.Completion != null) //completion percentage + { + message.AddMarkup(Loc.GetString("analysis-console-info-completion", + ("percentage", Math.Round(state.Completion.Value * 100)))+"\n"); + } + + Information.SetMessage(message); + } + + public void UpdateProgressBar(AnalysisConsoleScanUpdateState state) + { + ProgressBar.Visible = state.Scanning; + ProgressLabel.Visible = state.Scanning; + + if (!state.Scanning) + return; + + ProgressLabel.Text = Loc.GetString("analysis-console-progress-text", + ("seconds", (int) state.TotalTime.TotalSeconds - (int) state.TimeRemaining.TotalSeconds)); + ProgressBar.Value = (float) state.TimeRemaining.Divide(state.TotalTime); + } +} + diff --git a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs index 2ef6f53aa8..64e8441fdf 100644 --- a/Content.IntegrationTests/Tests/PrototypeSaveTest.cs +++ b/Content.IntegrationTests/Tests/PrototypeSaveTest.cs @@ -39,7 +39,6 @@ public sealed class PrototypeSaveTest // The rest of these prototypes (probably) shouldn't be getting ignored. // There should be an issue up tracking all of these prototypes, indicating that still need to get fixed. "HeadSkeleton", - "CrateArtifactContainer", // The followjng are all fixture-less phsyics entities that set can-collide to false on init. "CarpRift", "GasMinerOxygen", diff --git a/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs index 83004eb4dc..a1a1243d0e 100644 --- a/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs +++ b/Content.Server/Chemistry/ReactionEffects/FoamAreaReactionEffect.cs @@ -3,6 +3,7 @@ using Content.Server.Coordinates.Helpers; using Content.Shared.Audio; using Content.Shared.Chemistry.Components; using JetBrains.Annotations; +using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Player; @@ -19,7 +20,7 @@ namespace Content.Server.Chemistry.ReactionEffects } public static void SpawnFoam(string entityPrototype, EntityCoordinates coords, Solution? contents, int amount, float duration, float spreadDelay, - float removeDelay, SoundSpecifier sound, IEntityManager? entityManager = null) + float removeDelay, SoundSpecifier? sound = null, IEntityManager? entityManager = null) { entityManager ??= IoCManager.Resolve(); var ent = entityManager.SpawnEntity(entityPrototype, coords.SnapToGrid()); @@ -37,7 +38,8 @@ namespace Content.Server.Chemistry.ReactionEffects areaEffectComponent.TryAddSolution(contents); areaEffectComponent.Start(amount, duration, spreadDelay, removeDelay); - SoundSystem.Play(sound.GetSound(), Filter.Pvs(ent), ent, AudioHelpers.WithVariation(0.125f)); + entityManager.EntitySysManager.GetEntitySystem() + .PlayPvs(sound, ent, AudioParams.Default.WithVariation(0.125f)); } } } diff --git a/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs b/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs new file mode 100644 index 0000000000..04ae4ca6f5 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/ActivateArtifact.cs @@ -0,0 +1,13 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts; +using Content.Shared.Chemistry.Reagent; + +namespace Content.Server.Chemistry.ReagentEffects; + +public sealed class ActivateArtifact : ReagentEffect +{ + public override void Effect(ReagentEffectArgs args) + { + var artifact = args.EntityManager.EntitySysManager.GetEntitySystem(); + artifact.TryActivateArtifact(args.SolutionEntity); + } +} diff --git a/Content.Server/Salvage/SalvageMagnetComponent.cs b/Content.Server/Salvage/SalvageMagnetComponent.cs index 96b338f2f3..6ec6339467 100644 --- a/Content.Server/Salvage/SalvageMagnetComponent.cs +++ b/Content.Server/Salvage/SalvageMagnetComponent.cs @@ -54,6 +54,16 @@ namespace Content.Server.Salvage { public static readonly MagnetState Inactive = new (MagnetStateType.Inactive, TimeSpan.Zero); }; + + public sealed class SalvageMagnetActivatedEvent : EntityEventArgs + { + public EntityUid Magnet; + + public SalvageMagnetActivatedEvent(EntityUid magnet) + { + Magnet = magnet; + } + } public enum MagnetStateType { Inactive, diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index d03e512525..54b2eb693f 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -169,6 +169,7 @@ namespace Content.Server.Salvage } gridState.ActiveMagnets.Add(component); component.MagnetState = new MagnetState(MagnetStateType.Attaching, gridState.CurrentTime + AttachingTime); + RaiseLocalEvent(new SalvageMagnetActivatedEvent(component.Owner)); Report(component.Owner, component.SalvageChannel, "salvage-system-report-activate-success"); break; case MagnetStateType.Attaching: diff --git a/Content.Server/StationEvents/Events/BluespaceArtifact.cs b/Content.Server/StationEvents/Events/BluespaceArtifact.cs new file mode 100644 index 0000000000..fe203a9d53 --- /dev/null +++ b/Content.Server/StationEvents/Events/BluespaceArtifact.cs @@ -0,0 +1,46 @@ +using Robust.Shared.Random; + +namespace Content.Server.StationEvents.Events; + +public sealed class BluespaceArtifact : StationEventSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + public override string Prototype => "BluespaceArtifact"; + + public readonly string ArtifactSpawnerPrototype = "RandomArtifactSpawner"; + public readonly string ArtifactFlashPrototype = "EffectFlashBluespace"; + + public readonly List PossibleSighting = new() + { + "bluespace-artifact-sighting-1", + "bluespace-artifact-sighting-2", + "bluespace-artifact-sighting-3", + "bluespace-artifact-sighting-4", + "bluespace-artifact-sighting-5", + "bluespace-artifact-sighting-6", + "bluespace-artifact-sighting-7" + }; + + public override void Added() + { + base.Added(); + + var str = Loc.GetString("bluespace-artifact-event-announcement", + ("sighting", Loc.GetString(_random.Pick(PossibleSighting)))); + ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5")); + } + + public override void Started() + { + base.Started(); + + if (!TryFindRandomTile(out _, out _, out _, out var coords)) + return; + + EntityManager.SpawnEntity(ArtifactSpawnerPrototype, coords); + EntityManager.SpawnEntity(ArtifactFlashPrototype, coords); + + Sawmill.Info($"Spawning random artifact at {coords}"); + } +} diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs new file mode 100644 index 0000000000..f4b089636b --- /dev/null +++ b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveArtifactAnalyzerComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations; + +namespace Content.Server.Xenoarchaeology.Equipment.Components; + +/// +/// Activecomp used for tracking artifact analyzers that are currently +/// in the process of scanning an artifact. +/// +[RegisterComponent] +public sealed class ActiveArtifactAnalyzerComponent : Component +{ + /// + /// When did the scanning start? + /// + [DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))] + public TimeSpan StartTime; + + /// + /// What is being scanned? + /// + [ViewVariables] + public EntityUid Artifact; +} diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs new file mode 100644 index 0000000000..3475e2ab03 --- /dev/null +++ b/Content.Server/Xenoarchaeology/Equipment/Components/ActiveScannedArtifactComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Audio; + +namespace Content.Server.Xenoarchaeology.Equipment.Components; + +/// +/// This is used for tracking artifacts that are currently +/// being scanned by +/// +[RegisterComponent] +public sealed class ActiveScannedArtifactComponent : Component +{ + /// + /// The scanner that is scanning this artifact + /// + [ViewVariables] + public EntityUid Scanner; + + /// + /// The sound that plays when the scan fails + /// + public readonly SoundSpecifier ScanFailureSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg"); +} diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs new file mode 100644 index 0000000000..13e3b615ab --- /dev/null +++ b/Content.Server/Xenoarchaeology/Equipment/Components/AnalysisConsoleComponent.cs @@ -0,0 +1,31 @@ +using Content.Shared.MachineLinking; +using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Xenoarchaeology.Equipment.Components; + +/// +/// The console that is used for artifact analysis +/// +[RegisterComponent] +public sealed class AnalysisConsoleComponent : Component +{ + /// + /// The analyzer entity the console is linked. + /// Can be null if not linked. + /// + [ViewVariables(VVAccess.ReadWrite)] + public EntityUid? AnalyzerEntity; + + /// + /// The machine linking port for the analyzer + /// + [DataField("linkingPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + public readonly string LinkingPort = "ArtifactAnalyzerSender"; + + /// + /// The sound played when an artifact is destroyed. + /// + [DataField("destroySound")] + public SoundSpecifier DestroySound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg"); +} diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs new file mode 100644 index 0000000000..19d56c0482 --- /dev/null +++ b/Content.Server/Xenoarchaeology/Equipment/Components/ArtifactAnalyzerComponent.cs @@ -0,0 +1,67 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Xenoarchaeology.Equipment.Components; + +/// +/// A machine that is combined and linked to the +/// in order to analyze and destroy artifacts. +/// +[RegisterComponent] +public sealed class ArtifactAnalyzerComponent : Component +{ + /// + /// How long it takes to analyze an artifact + /// + [DataField("analysisDuration", customTypeSerializer: typeof(TimespanSerializer))] + public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(60); + + /// + /// A mulitplier on the duration of analysis. + /// Used for machine upgrading. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float AnalysisDurationMulitplier = 1; + + /// + /// The machine part that modifies analysis duration. + /// + [DataField("machinePartAnalysisDuration", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartAnalysisDuration = "ScanningModule"; + + /// + /// The modifier raised to the part rating to determine the duration multiplier. + /// + [DataField("partRatingAnalysisDurationMultiplier")] + public float PartRatingAnalysisDurationMultiplier = 0.75f; + + /// + /// The corresponding console entity. + /// Can be null if not linked. + /// + [ViewVariables] + public EntityUid? Console; + + /// + /// All of the valid artifacts currently touching the analyzer. + /// + [ViewVariables] + public HashSet Contacts = new(); + + [DataField("scanFinishedSound")] + public readonly SoundSpecifier ScanFinishedSound = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg"); + + #region Analysis Data + [ViewVariables] + public EntityUid? LastAnalyzedArtifact; + + [ViewVariables] + public ArtifactNode? LastAnalyzedNode; + + [ViewVariables(VVAccess.ReadWrite)] + public float? LastAnalyzedCompletion; + #endregion +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Components/SuppressArtifactContainerComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs similarity index 71% rename from Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Components/SuppressArtifactContainerComponent.cs rename to Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs index fddcf44438..7f210f6efd 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Components/SuppressArtifactContainerComponent.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Components/SuppressArtifactContainerComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Components; +namespace Content.Server.Xenoarchaeology.Equipment.Components; /// /// Suppress artifact activation, when entity is placed inside this container. diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs new file mode 100644 index 0000000000..af9092277c --- /dev/null +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs @@ -0,0 +1,413 @@ +using System.Linq; +using Content.Server.Construction; +using Content.Server.MachineLinking.Events; +using Content.Server.Power.Components; +using Content.Server.Research; +using Content.Server.Research.Components; +using Content.Server.UserInterface; +using Content.Server.Xenoarchaeology.Equipment.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Audio; +using Content.Shared.MachineLinking.Events; +using Content.Shared.Popups; +using Content.Shared.Research.Components; +using Content.Shared.Xenoarchaeology.Equipment; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Audio; +using Robust.Shared.Physics.Events; +using Robust.Shared.Player; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Server.Xenoarchaeology.Equipment.Systems; + +/// +/// This system is used for managing the artifact analyzer as well as the analysis console. +/// It also hanadles scanning and ui updates for both systems. +/// +public sealed class ArtifactAnalyzerSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly ArtifactSystem _artifact = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnScannedMoved); + SubscribeLocalEvent(OnArtifactActivated); + + SubscribeLocalEvent(OnAnalyzeStart); + SubscribeLocalEvent(OnAnalyzeEnd); + SubscribeLocalEvent(OnPowerChanged); + + SubscribeLocalEvent(OnUpgradeExamine); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnCollide); + SubscribeLocalEvent(OnEndCollide); + + SubscribeLocalEvent(OnNewLink); + SubscribeLocalEvent(OnPortDisconnected); + + SubscribeLocalEvent(OnServerSelectionMessage); + SubscribeLocalEvent(OnScanButton); + SubscribeLocalEvent(OnDestroyButton); + + SubscribeLocalEvent((e,c,_) => UpdateUserInterface(e,c), + after: new []{typeof(ResearchSystem)}); + SubscribeLocalEvent((e,c,_) => UpdateUserInterface(e,c), + after: new []{typeof(ResearchSystem)}); + SubscribeLocalEvent((e,c,_) => UpdateUserInterface(e,c)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var (active, scan) in EntityQuery()) + { + if (scan.Console != null) + UpdateUserInterface(scan.Console.Value); + + if (_timing.CurTime - active.StartTime < (scan.AnalysisDuration * scan.AnalysisDurationMulitplier)) + continue; + + FinishScan(scan.Owner, scan, active); + } + } + + /// + /// Resets the current scan on the artifact analyzer + /// + /// The analyzer being reset + /// + [PublicAPI] + public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + component.LastAnalyzedArtifact = null; + UpdateAnalyzerInformation(uid, component); + } + + /// + /// Goes through the current contacts on + /// the analyzer and returns a valid artifact + /// + /// + /// + /// + private EntityUid? GetArtifactForAnalysis(EntityUid? uid, ArtifactAnalyzerComponent? component = null) + { + if (uid == null) + return null; + + if (!Resolve(uid.Value, ref component)) + return null; + + var validEnts = component.Contacts.Where(HasComp).ToHashSet(); + return validEnts.FirstOrNull(); + } + + /// + /// Updates the current scan information based on + /// the last artifact that was scanned. + /// + /// + /// + private void UpdateAnalyzerInformation(EntityUid uid, ArtifactAnalyzerComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.LastAnalyzedArtifact == null) + { + component.LastAnalyzedCompletion = null; + component.LastAnalyzedNode = null; + } + else if (TryComp(component.LastAnalyzedArtifact, out var artifact)) + { + var lastNode = (ArtifactNode?) artifact.CurrentNode?.Clone(); + component.LastAnalyzedNode = lastNode; + + if (artifact.NodeTree != null) + { + var discoveredNodes = artifact.NodeTree.AllNodes.Count(x => x.Discovered && x.Triggered); + component.LastAnalyzedCompletion = (float) discoveredNodes / artifact.NodeTree.AllNodes.Count; + } + } + } + + private void OnNewLink(EntityUid uid, AnalysisConsoleComponent component, NewLinkEvent args) + { + if (!TryComp(args.Receiver, out var analyzer)) + return; + + component.AnalyzerEntity = args.Receiver; + analyzer.Console = uid; + + UpdateUserInterface(uid, component); + } + + private void OnPortDisconnected(EntityUid uid, AnalysisConsoleComponent component, PortDisconnectedEvent args) + { + if (args.Port == component.LinkingPort && component.AnalyzerEntity != null) + { + if (TryComp(component.AnalyzerEntity, out var analyzezr)) + analyzezr.Console = null; + component.AnalyzerEntity = null; + } + + UpdateUserInterface(uid, component); + } + + private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + EntityUid? artifact = null; + ArtifactNode? node = null; + float? completion = null; + var totalTime = TimeSpan.Zero; + var canScan = false; + if (component.AnalyzerEntity != null && TryComp(component.AnalyzerEntity, out var analyzer)) + { + artifact = analyzer.LastAnalyzedArtifact; + node = analyzer.LastAnalyzedNode; + completion = analyzer.LastAnalyzedCompletion; + totalTime = analyzer.AnalysisDuration * analyzer.AnalysisDurationMulitplier; + canScan = analyzer.Contacts.Any(); + } + + var analyzerConnected = component.AnalyzerEntity != null; + var serverConnected = TryComp(uid, out var client) && client.ConnectedToServer; + + var scanning = TryComp(component.AnalyzerEntity, out var active); + var remaining = active != null ? _timing.CurTime - active.StartTime : TimeSpan.Zero; + + var state = new AnalysisConsoleScanUpdateState(artifact, analyzerConnected, serverConnected, canScan, + node?.Id, node?.Depth, node?.Edges.Count, node?.Triggered, node?.Effect.ID, node?.Trigger.ID, completion, + scanning, remaining, totalTime); + + var bui = _ui.GetUi(uid, ArtifactAnalzyerUiKey.Key); + _ui.SetUiState(bui, state); + } + + /// + /// opens the server selection menu. + /// + /// + /// + /// + private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args) + { + _ui.TryOpen(uid, ResearchClientUiKey.Key, (IPlayerSession) args.Session); + } + + /// + /// Starts scanning the artifact. + /// + /// + /// + /// + private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args) + { + if (component.AnalyzerEntity == null) + return; + + if (HasComp(component.AnalyzerEntity)) + return; + + var ent = GetArtifactForAnalysis(component.AnalyzerEntity); + if (ent == null) + return; + + var activeComp = EnsureComp(component.AnalyzerEntity.Value); + activeComp.StartTime = _timing.CurTime; + activeComp.Artifact = ent.Value; + + var activeArtifact = EnsureComp(ent.Value); + activeArtifact.Scanner = component.AnalyzerEntity.Value; + } + + /// + /// destroys the artifact and updates the server points + /// + /// + /// + /// + private void OnDestroyButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleDestroyButtonPressedMessage args) + { + if (!TryComp(uid, out var client) || client.Server == null || component.AnalyzerEntity == null) + return; + + var entToDestroy = GetArtifactForAnalysis(component.AnalyzerEntity); + if (entToDestroy == null) + return; + + if (TryComp(component.AnalyzerEntity.Value, out var analyzer) && + analyzer.LastAnalyzedArtifact == entToDestroy) + { + ResetAnalyzer(component.AnalyzerEntity.Value); + } + + client.Server.Points += _artifact.GetResearchPointValue(entToDestroy.Value); + EntityManager.DeleteEntity(entToDestroy.Value); + + _audio.PlayPvs(component.DestroySound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f)); + + _popup.PopupEntity(Loc.GetString("analyzer-artifact-destroy-popup"), + component.AnalyzerEntity.Value, Filter.Pvs(component.AnalyzerEntity.Value), PopupType.Large); + + UpdateUserInterface(uid, component); + } + + /// + /// Cancels scans if the artifact changes nodes (is activated) during the scan. + /// + /// + /// + /// + private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args) + { + CancelScan(uid); + } + + /// + /// Checks to make sure that the currently scanned artifact isn't moved off of the scanner + /// + /// + /// + /// + private void OnScannedMoved(EntityUid uid, ActiveScannedArtifactComponent component, ref MoveEvent args) + { + if (!TryComp(component.Scanner, out var analyzer)) + return; + + if (analyzer.Contacts.Contains(uid)) + return; + + CancelScan(uid, component, analyzer); + } + + /// + /// Stops the current scan + /// + /// The artifact being scanned + /// + /// The artifact analyzer component + [PublicAPI] + public void CancelScan(EntityUid artifact, ActiveScannedArtifactComponent? component = null, ArtifactAnalyzerComponent? analyzer = null) + { + if (!Resolve(artifact, ref component, false)) + return; + + if (!Resolve(component.Scanner, ref analyzer)) + return; + + _audio.PlayPvs(component.ScanFailureSound, component.Scanner, AudioParams.Default.WithVolume(3f)); + + RemComp(component.Scanner); + if (analyzer.Console != null) + UpdateUserInterface(analyzer.Console.Value); + + RemCompDeferred(artifact, component); + } + + /// + /// Finishes the current scan. + /// + /// The analyzer that is scanning + /// + /// + [PublicAPI] + public void FinishScan(EntityUid uid, ArtifactAnalyzerComponent? component = null, ActiveArtifactAnalyzerComponent? active = null) + { + if (!Resolve(uid, ref component, ref active)) + return; + + _audio.PlayPvs(component.ScanFinishedSound, uid); + component.LastAnalyzedArtifact = active.Artifact; + UpdateAnalyzerInformation(uid, component); + + RemComp(active.Artifact); + RemComp(uid, active); + if (component.Console != null) + UpdateUserInterface(component.Console.Value); + } + + private void OnRefreshParts(EntityUid uid, ArtifactAnalyzerComponent component, RefreshPartsEvent args) + { + var analysisRating = args.PartRatings[component.MachinePartAnalysisDuration]; + + component.AnalysisDurationMulitplier = MathF.Pow(component.PartRatingAnalysisDurationMultiplier, analysisRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, ArtifactAnalyzerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("analyzer-artifact-component-upgrade-analysis", component.AnalysisDurationMulitplier); + } + + private void OnCollide(EntityUid uid, ArtifactAnalyzerComponent component, ref StartCollideEvent args) + { + var otherEnt = args.OtherFixture.Body.Owner; + + if (!HasComp(otherEnt)) + return; + + component.Contacts.Add(otherEnt); + + if (component.Console != null) + UpdateUserInterface(component.Console.Value); + } + + private void OnEndCollide(EntityUid uid, ArtifactAnalyzerComponent component, ref EndCollideEvent args) + { + var otherEnt = args.OtherFixture.Body.Owner; + + if (!HasComp(otherEnt)) + return; + component.Contacts.Remove(otherEnt); + + if (component.Console != null) + UpdateUserInterface(component.Console.Value); + } + + private void OnAnalyzeStart(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentStartup args) + { + if (TryComp(uid, out var powa)) + powa.NeedsPower = true; + + if (TryComp(uid, out var ambientSound)) + { + ambientSound.Enabled = true; + Dirty(ambientSound); + } + } + + private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args) + { + if (TryComp(uid, out var powa)) + powa.NeedsPower = false; + + if (TryComp(uid, out var ambientSound)) + { + ambientSound.Enabled = false; + Dirty(ambientSound); + } + } + + private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent component, ref PowerChangedEvent args) + { + if (!args.Powered) + CancelScan(component.Artifact); + } +} + diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Systems/SuppressArtifactContainerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs similarity index 58% rename from Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Systems/SuppressArtifactContainerSystem.cs rename to Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs index f60f86801c..16aea2a3b3 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Equipment/Systems/SuppressArtifactContainerSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/SuppressArtifactContainerSystem.cs @@ -1,16 +1,11 @@ -using Content.Server.Cargo.Components; -using Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Components; +using Content.Server.Xenoarchaeology.Equipment.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts; using Robust.Shared.Containers; -namespace Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Systems; +namespace Content.Server.Xenoarchaeology.Equipment.Systems; public sealed class SuppressArtifactContainerSystem : EntitySystem { - /// - /// Artifacts go from 2k to 4k, 1.5k net profit (considering the container price - /// - public const double ContainedArtifactModifier = 2; - public override void Initialize() { base.Initialize(); @@ -24,11 +19,6 @@ public sealed class SuppressArtifactContainerSystem : EntitySystem return; artifact.IsSuppressed = true; - - if (TryComp(args.Entity, out var price)) - { - price.Price *= ContainedArtifactModifier; - } } private void OnRemoved(EntityUid uid, SuppressArtifactContainerComponent component, EntRemovedFromContainerMessage args) @@ -37,10 +27,5 @@ public sealed class SuppressArtifactContainerSystem : EntitySystem return; artifact.IsSuppressed = false; - - if (TryComp(args.Entity, out var price)) - { - price.Price /= ContainedArtifactModifier; - } } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs index 17e23c4e81..46b9e067de 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactComponent.cs @@ -1,39 +1,143 @@ +using Content.Shared.Xenoarchaeology.XenoArtifacts; +using Robust.Shared.Serialization.TypeSerializers.Implementations; + namespace Content.Server.Xenoarchaeology.XenoArtifacts; [RegisterComponent] public sealed class ArtifactComponent : Component { /// - /// Should artifact pick a random trigger on startup? + /// The artifact's node tree. /// - [DataField("randomTrigger")] - public bool RandomTrigger = true; + [ViewVariables] + public ArtifactTree? NodeTree; /// - /// List of all possible triggers activations. - /// Should be same as components names. + /// The current node the artifact is on. /// - [DataField("possibleTriggers")] - public string[] PossibleTriggers = { - "ArtifactInteractionTrigger", - "ArtifactGasTrigger", - "ArtifactHeatTrigger", - "ArtifactElectricityTrigger", - }; + [ViewVariables] + public ArtifactNode? CurrentNode; + + #region Node Tree Gen + /// + /// Minimum number of nodes to generate, inclusive + /// + [DataField("nodesMin")] + public int NodesMin = 3; /// - /// Cooldown time between artifact activations (in seconds). + /// Maximum number of nodes to generate, exclusive /// - [DataField("timer")] + [DataField("nodesMax")] + public int NodesMax = 9; + #endregion + + /// + /// Cooldown time between artifact activations (in seconds). + /// + [DataField("timer", customTypeSerializer: typeof(TimespanSerializer))] [ViewVariables(VVAccess.ReadWrite)] - public double CooldownTime = 10; + public TimeSpan CooldownTime = TimeSpan.FromSeconds(5); /// - /// Is this artifact under some suppression device? - /// If true, will ignore all trigger activations attempts. + /// Is this artifact under some suppression device? + /// f true, will ignore all trigger activations attempts. /// [ViewVariables(VVAccess.ReadWrite)] public bool IsSuppressed; + /// + /// The last time the artifact was activated. + /// + [DataField("lastActivationTime", customTypeSerializer: typeof(TimespanSerializer))] public TimeSpan LastActivationTime; } + +/// +/// A tree of nodes. +/// +[DataDefinition] +public sealed class ArtifactTree +{ + /// + /// The first node of the tree + /// + [ViewVariables] + public ArtifactNode StartNode = default!; + + /// + /// Every node contained in the tree + /// + [ViewVariables] + public readonly List AllNodes = new(); +} + +/// +/// A single "node" of an artifact that contains various data about it. +/// +[DataDefinition] +public sealed class ArtifactNode : ICloneable +{ + /// + /// A numeric id corresponding to each node. used for display purposes + /// + [ViewVariables] + public int Id; + + /// + /// how "deep" into the node tree. used for generation and price/value calculations + /// + [ViewVariables] + public int Depth = 0; + + /// + /// A list of surrounding nodes. Used for tree traversal + /// + [ViewVariables] + public List Edges = new(); + + /// + /// Whether or not the node has been entered + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool Discovered = false; + + /// + /// The trigger for the node + /// + [ViewVariables] + public ArtifactTriggerPrototype Trigger = default!; + + /// + /// Whether or not the node has been triggered + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool Triggered = false; + + /// + /// The effect when the node is activated + /// + [ViewVariables] + public ArtifactEffectPrototype Effect = default!; + + /// + /// Used for storing cumulative information about nodes + /// + [ViewVariables] + public Dictionary NodeData = new(); + + public object Clone() + { + return new ArtifactNode + { + Id = Id, + Depth = Depth, + Edges = Edges, + Discovered = Discovered, + Trigger = Trigger, + Triggered = Triggered, + Effect = Effect, + NodeData = NodeData + }; + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs new file mode 100644 index 0000000000..d616637dcf --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs @@ -0,0 +1,198 @@ +using System.Linq; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Xenoarchaeology.XenoArtifacts; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Serialization.Manager; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts; + +public sealed partial class ArtifactSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; + + private const int MaxEdgesPerNode = 3; + + /// + /// Generate an Artifact tree with fully developed nodes. + /// + /// The tree being generated. + /// The amount of nodes it has. + private void GenerateArtifactNodeTree(ref ArtifactTree tree, int nodeAmount) + { + if (nodeAmount < 1) + { + Logger.Error($"nodeAmount {nodeAmount} is less than 1. Aborting artifact tree generation."); + return; + } + + var uninitializedNodes = new List { new() }; + tree.StartNode = uninitializedNodes.First(); //the first node + + while (uninitializedNodes.Any()) + { + GenerateNode(ref uninitializedNodes, ref tree, nodeAmount); + } + } + + /// + /// Generate an individual node on the tree. + /// + private void GenerateNode(ref List uninitializedNodes, ref ArtifactTree tree, int targetNodeAmount) + { + if (!uninitializedNodes.Any()) + return; + + var node = uninitializedNodes.First(); + uninitializedNodes.Remove(node); + + node.Id = _random.Next(0, 10000); + + //Generate the connected nodes + var maxEdges = Math.Max(1, targetNodeAmount - tree.AllNodes.Count - uninitializedNodes.Count - 1); + maxEdges = Math.Min(maxEdges, MaxEdgesPerNode); + var minEdges = Math.Clamp(targetNodeAmount - tree.AllNodes.Count - uninitializedNodes.Count - 1, 0, 1); + + var edgeAmount = _random.Next(minEdges, maxEdges); + + for (var i = 0; i < edgeAmount; i++) + { + var neighbor = new ArtifactNode + { + Depth = node.Depth + 1 + }; + node.Edges.Add(neighbor); + neighbor.Edges.Add(node); + + uninitializedNodes.Add(neighbor); + } + + node.Trigger = GetRandomTrigger(ref node); + node.Effect = GetRandomEffect(ref node); + + tree.AllNodes.Add(node); + } + + //yeah these two functions are near duplicates but i don't + //want to implement an interface or abstract parent + + private ArtifactTriggerPrototype GetRandomTrigger(ref ArtifactNode node) + { + var allTriggers = _prototype.EnumeratePrototypes().ToList(); + var validDepth = allTriggers.Select(x => x.TargetDepth).Distinct().ToList(); + + var weights = GetDepthWeights(validDepth, node.Depth); + var selectedRandomTargetDepth = GetRandomTargetDepth(weights); + var targetTriggers = allTriggers.Where(x => + x.TargetDepth == selectedRandomTargetDepth).ToList(); + + return _random.Pick(targetTriggers); + } + + private ArtifactEffectPrototype GetRandomEffect(ref ArtifactNode node) + { + var allEffects = _prototype.EnumeratePrototypes().ToList(); + var validDepth = allEffects.Select(x => x.TargetDepth).Distinct().ToList(); + + var weights = GetDepthWeights(validDepth, node.Depth); + var selectedRandomTargetDepth = GetRandomTargetDepth(weights); + var targetEffects = allEffects.Where(x => + x.TargetDepth == selectedRandomTargetDepth).ToList(); + + return _random.Pick(targetEffects); + } + + /// + /// The goal is that the depth that is closest to targetDepth has the highest chance of appearing. + /// The issue is that we also want some variance, so levels that are +/- 1 should also have a + /// decent shot of appearing. This function should probably get some tweaking at some point. + /// + private Dictionary GetDepthWeights(IEnumerable depths, int targetDepth) + { + var weights = new Dictionary(); + foreach (var d in depths) + { + //TODO: is this equation sus? idk. -emo + // 0.3 / (|current_iterated_depth - our_actual_depth| + 1)^2 + var w = 0.3f / MathF.Pow(Math.Abs(d - targetDepth) + 1, 2); + weights.Add(d, w); + } + return weights; + } + + /// + /// Uses a weighted random system to get a random depth. + /// + private int GetRandomTargetDepth(Dictionary weights) + { + var sum = weights.Values.Sum(); + var accumulated = 0f; + + var rand = _random.NextFloat() * sum; + + foreach (var (key, weight) in weights) + { + accumulated += weight; + + if (accumulated >= rand) + { + return key; + } + } + return _random.Pick(weights.Keys); //shouldn't happen + } + + /// + /// Enter a node: attach the relevant components + /// + private void EnterNode(EntityUid uid, ref ArtifactNode node, ArtifactComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.CurrentNode != null) + { + ExitNode(uid, component); + } + + component.CurrentNode = node; + node.Discovered = true; + + var allComponents = node.Effect.Components.Concat(node.Effect.PermanentComponents).Concat(node.Trigger.Components); + foreach (var (name, entry) in allComponents) + { + var reg = _componentFactory.GetRegistration(name); + var comp = (Component) _componentFactory.GetComponent(reg); + comp.Owner = uid; + + var temp = (object) comp; + _serialization.Copy(entry.Component, ref temp); + EntityManager.AddComponent(uid, (Component) temp!, true); + } + + RaiseLocalEvent(uid, new ArtifactNodeEnteredEvent(component.CurrentNode.Id)); + } + + /// + /// Exit a node: remove the relevant components. + /// + private void ExitNode(EntityUid uid, ArtifactComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var node = component.CurrentNode; + if (node == null) + return; + + foreach (var name in node.Effect.Components.Keys.Concat(node.Trigger.Components.Keys)) + { + var comp = _componentFactory.GetRegistration(name); + EntityManager.RemoveComponentDeferred(uid, comp.Type); + } + + component.CurrentNode = null; + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs index dee74ac09c..ab0d43a523 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs @@ -1,50 +1,122 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Cargo.Systems; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using JetBrains.Annotations; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server.Xenoarchaeology.XenoArtifacts; -public sealed class ArtifactSystem : EntitySystem +public sealed partial class ArtifactSystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IComponentFactory _componentFactory = default!; + + private const int PricePerNode = 500; + private const int PointsPerNode = 5000; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(GetPrice); } private void OnInit(EntityUid uid, ArtifactComponent component, MapInitEvent args) { - if (component.RandomTrigger) - { - AddRandomTrigger(uid, component); - } + RandomizeArtifact(component); } - private void AddRandomTrigger(EntityUid uid, ArtifactComponent? component = null) + /// + /// Calculates the price of an artifact based on + /// how many nodes have been unlocked/triggered + /// + /// + /// General balancing (for fully unlocked artifacts): + /// Simple (1-2 Nodes): 1-2K + /// Medium (5-8 Nodes): 6-7K + /// Complex (7-12 Nodes): 10-11K + /// + private void GetPrice(EntityUid uid, ArtifactComponent component, ref PriceCalculationEvent args) { - if (!Resolve(uid, ref component)) + if (component.NodeTree == null) return; - var triggerName = _random.Pick(component.PossibleTriggers); - var trigger = (Component) _componentFactory.GetComponent(triggerName); - trigger.Owner = uid; + var price = component.NodeTree.AllNodes.Sum(GetNodePrice); - if (EntityManager.HasComponent(uid, trigger.GetType())) - { - Logger.Error($"Attempted to add a random artifact trigger ({triggerName}) to an entity ({ToPrettyString(uid)}), but it already has the trigger"); - return; - } + // 25% bonus for fully exploring every node. + var fullyExploredBonus = component.NodeTree.AllNodes.Any(x => !x.Triggered) ? 1 : 1.25f; - EntityManager.AddComponent(uid, trigger); - RaiseLocalEvent(uid, new RandomizeTriggerEvent(), true); + args.Price =+ price * fullyExploredBonus; } - public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null, - ArtifactComponent? component = null) + private float GetNodePrice(ArtifactNode node) + { + if (!node.Discovered) //no money for undiscovered nodes. + return 0; + + //quarter price if not triggered + var priceMultiplier = node.Triggered ? 1f : 0.25f; + //the danger is the average of node depth, effect danger, and trigger danger. + var nodeDanger = (node.Depth + node.Effect.TargetDepth + node.Trigger.TargetDepth) / 3; + + var price = MathF.Pow(2f, nodeDanger) * PricePerNode * priceMultiplier; + return price; + } + + /// + /// Calculates how many research points the artifact is worht + /// + /// + /// Rebalance this shit at some point. Definitely OP. + /// + public int GetResearchPointValue(EntityUid uid, ArtifactComponent? component = null) + { + if (!Resolve(uid, ref component) || component.NodeTree == null) + return 0; + + var sumValue = component.NodeTree.AllNodes.Sum(GetNodePointValue); + var fullyExploredBonus = component.NodeTree.AllNodes.Any(x => !x.Triggered) ? 1 : 1.25f; + + var pointValue = (int) (sumValue * fullyExploredBonus); + return pointValue; + } + + private float GetNodePointValue(ArtifactNode node) + { + if (!node.Discovered) + return 0; + + var valueDeduction = !node.Triggered ? 0.5f : 1; + var nodeDanger = (node.Depth + node.Effect.TargetDepth + node.Trigger.TargetDepth) / 3; + + return (nodeDanger+1) * PointsPerNode * valueDeduction; + } + + /// + /// Randomize a given artifact. + /// + [PublicAPI] + public void RandomizeArtifact(ArtifactComponent component) + { + var nodeAmount = _random.Next(component.NodesMin, component.NodesMax); + + component.NodeTree = new ArtifactTree(); + + GenerateArtifactNodeTree(ref component.NodeTree, nodeAmount); + EnterNode(component.Owner, ref component.NodeTree.StartNode, component); + } + + /// + /// Tries to activate the artifact + /// + /// + /// + /// + /// + public bool TryActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null) { if (!Resolve(uid, ref component)) return false; @@ -55,25 +127,85 @@ public sealed class ArtifactSystem : EntitySystem // check if artifact isn't under cooldown var timeDif = _gameTiming.CurTime - component.LastActivationTime; - if (timeDif.TotalSeconds < component.CooldownTime) + if (timeDif < component.CooldownTime) return false; ForceActivateArtifact(uid, user, component); return true; } - public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, - ArtifactComponent? component = null) + /// + /// Forces an artifact to activate + /// + /// + /// + /// + public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, ArtifactComponent? component = null) { if (!Resolve(uid, ref component)) return; + if (component.CurrentNode == null) + return; component.LastActivationTime = _gameTiming.CurTime; - var ev = new ArtifactActivatedEvent() + var ev = new ArtifactActivatedEvent { Activator = user }; RaiseLocalEvent(uid, ev, true); + + component.CurrentNode.Triggered = true; + if (component.CurrentNode.Edges.Any()) + { + var newNode = _random.Pick(component.CurrentNode.Edges); + EnterNode(uid, ref newNode, component); + } + } + + /// + /// Try and get a data object from a node + /// + /// The entity you're getting the data from + /// The data's key + /// The data you are trying to get. + /// + /// + /// + public bool TryGetNodeData(EntityUid uid, string key, [NotNullWhen(true)] out T data, ArtifactComponent? component = null) + { + data = default!; + + if (!Resolve(uid, ref component)) + return false; + + if (component.CurrentNode == null) + return false; + + if (component.CurrentNode.NodeData.TryGetValue(key, out var dat) && dat is T value) + { + data = value; + return true; + } + + return false; + } + + /// + /// Sets the node data to a certain value + /// + /// The artifact + /// The key being set + /// The value it's being set to + /// + public void SetNodeData(EntityUid uid, string key, object value, ArtifactComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + if (component.CurrentNode == null) + return; + + component.CurrentNode.NodeData[key] = value; } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs new file mode 100644 index 0000000000..530f730687 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DamageNearbyArtifactComponent.cs @@ -0,0 +1,44 @@ +using Content.Shared.Damage; +using Content.Shared.Whitelist; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; + +/// +/// When activated, damages nearby entities. +/// +[RegisterComponent] +public sealed class DamageNearbyArtifactComponent : Component +{ + /// + /// The radius of entities that will be affected + /// + [DataField("radius")] + public float Radius = 3f; + + /// + /// A whitelist for filtering certain damage. + /// + /// + /// TODO: The component portion, since it uses an array, does not work currently. + /// + [DataField("whitelist")] + public EntityWhitelist? Whitelist; + + /// + /// The damage that is applied + /// + [DataField("damage", required: true)] + public DamageSpecifier Damage = default!; + + /// + /// The chance that damage is applied to each individual entity + /// + [DataField("damageChance")] + public float DamageChance = 1f; + + /// + /// Whether or not this should ignore resistances for the damage + /// + [DataField("ignoreResistances")] + public bool IgnoreResistances; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs index 91ffa08e9d..0136b68667 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/DiseaseArtifactComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Disease; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; /// @@ -8,10 +9,15 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; public sealed class DiseaseArtifactComponent : Component { /// - /// Disease the artifact will spawn - /// If empty, picks a random one from its list + /// The diseases that the artifact can use. + /// + [DataField("diseasePrototype", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List DiseasePrototypes = new(); + + /// + /// Disease the artifact will spawn + /// Picks a random one from its list /// - [DataField("disease")] [ViewVariables(VVAccess.ReadWrite)] public DiseasePrototype? SpawnDisease; @@ -19,7 +25,6 @@ public sealed class DiseaseArtifactComponent : Component /// How far away it will check for people /// If empty, picks a random one from its list /// - [DataField("range")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField("range"), ViewVariables(VVAccess.ReadWrite)] public float Range = 5f; } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs new file mode 100644 index 0000000000..7dfec6db86 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/FoamArtifactComponent.cs @@ -0,0 +1,54 @@ +using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; + +/// +/// Generates foam from the artifact when activated +/// +[RegisterComponent] +public sealed class FoamArtifactComponent : Component +{ + /// + /// The list of reagents that will randomly be picked from + /// to choose the foam reagent + /// + [DataField("reagents", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List Reagents = new(); + + /// + /// The foam reagent + /// + [ViewVariables(VVAccess.ReadWrite)] + public string? SelectedReagent; + + /// + /// How long does the foam last? + /// + [DataField("duration")] + public float Duration = 10; + + /// + /// How much reagent is in the foam? + /// + [DataField("reagentAmount")] + public float ReagentAmount = 100; + + /// + /// Minimum radius of foam spawned + /// + [DataField("minFoamAmount")] + public int MinFoamAmount = 2; + + /// + /// Maximum radius of foam spawned + /// + [DataField("maxFoamAmount")] + public int MaxFoamAmount = 6; + + /// + /// How long it takes for each tile of foam to spawn + /// + [DataField("spreadDuration")] + public float SpreadDuration = 1; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs index 2239b8b9e2..4d9d9b5ac9 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/GasArtifactComponent.cs @@ -20,7 +20,7 @@ public sealed class GasArtifactComponent : Component /// List of possible activation gases to pick on startup. /// [DataField("possibleGas")] - public Gas[] PossibleGases = + public List PossibleGases = new() { Gas.Oxygen, Gas.Plasma, diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs new file mode 100644 index 0000000000..ee4b804827 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/KnockArtifactComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; + +/// +/// This is used for using the "knock" spell when the artifact is activated +/// +[RegisterComponent] +public sealed class KnockArtifactComponent : Component +{ + /// + /// The range of the spell + /// + [DataField("knockRange")] + public float KnockRange = 4f; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs new file mode 100644 index 0000000000..9f8a7a304f --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/LightFlickerArtifactComponent.cs @@ -0,0 +1,20 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; + +/// +/// Flickers all the lights within a certain radius. +/// +[RegisterComponent] +public sealed class LightFlickerArtifactComponent : Component +{ + /// + /// Lights within this radius will be flickered on activation + /// + [DataField("radius")] + public float Radius = 4; + + /// + /// The chance that the light will flicker + /// + [DataField("flickerChance")] + public float FlickerChance = 0.75f; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RadiateArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RadiateArtifactComponent.cs deleted file mode 100644 index 1e57e46b23..0000000000 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RadiateArtifactComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; - -/// -/// Spawn RadiationPulse when artifact activated. -/// -[RegisterComponent] -public sealed class RadiateArtifactComponent : Component -{ - /// - /// Radiation pulse prototype to spawn. - /// - [DataField("pulsePrototype", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string PulsePrototype = "RadiationPulse"; -} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs new file mode 100644 index 0000000000..ce9d823a4f --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/RandomTeleportArtifactComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; + +/// +/// When activated, will teleport the artifact +/// to a random position within a certain radius +/// +[RegisterComponent] +public sealed class RandomTeleportArtifactComponent : Component +{ + /// + /// The max distance that the artifact will teleport. + /// + [DataField("range")] + public float Range = 7.5f; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs new file mode 100644 index 0000000000..aac7a0fe28 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ShuffleArtifactComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; + +/// +/// When activated, will shuffle the position of all players +/// within a certain radius. +/// +[RegisterComponent] +public sealed class ShuffleArtifactComponent : Component +{ + [DataField("radius")] + public float Radius = 7.5f; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs index e42f50d42f..4cf5a3d0a9 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/SpawnArtifactComponent.cs @@ -11,21 +11,35 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; [RegisterComponent] public sealed class SpawnArtifactComponent : Component { - [DataField("random")] - public bool RandomPrototype = true; - + /// + /// The list of possible prototypes to spawn that it picks from. + /// [DataField("possiblePrototypes", customTypeSerializer:typeof(PrototypeIdListSerializer))] public List PossiblePrototypes = new(); + /// + /// The prototype it selected to spawn. + /// [ViewVariables(VVAccess.ReadWrite)] [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer))] public string? Prototype; + /// + /// The range around the artifact that it will spawn the entity + /// [DataField("range")] public float Range = 0.5f; + /// + /// The maximum number of times the spawn will occur + /// [DataField("maxSpawns")] public int MaxSpawns = 20; - public int SpawnsCount = 0; + /// + /// Whether or not the artifact spawns the same entity every time + /// or picks through the list each time. + /// + [DataField("consistentSpawn")] + public bool ConsistentSpawn = true; } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs index 9595e75c0f..523fb43fde 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TelepathicArtifactComponent.cs @@ -13,7 +13,7 @@ public sealed class TelepathicArtifactComponent : Component /// [DataField("messages")] [ViewVariables(VVAccess.ReadWrite)] - public string[] Messages = default!; + public List Messages = default!; /// /// Loc string ids of telepathic messages (spooky version). @@ -21,7 +21,7 @@ public sealed class TelepathicArtifactComponent : Component /// [DataField("drastic")] [ViewVariables(VVAccess.ReadWrite)] - public string[] DrasticMessages = default!; + public List? DrasticMessages; /// /// Probability to pick drastic version of message. diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs index abc7fcf76b..e77d387e14 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/TemperatureArtifactComponent.cs @@ -8,7 +8,7 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; [RegisterComponent] public sealed class TemperatureArtifactComponent : Component { - [DataField("targetTemp")] + [DataField("targetTemp"), ViewVariables(VVAccess.ReadWrite)] public float TargetTemperature = Atmospherics.T0C; [DataField("spawnTemp")] @@ -21,6 +21,6 @@ public sealed class TemperatureArtifactComponent : Component /// If true, artifact will heat/cool not only its current tile, but surrounding tiles too. /// This will change room temperature much faster. /// - [DataField("effectAdjacent")] - public bool EffectAdjacentTiles = true; + [DataField("affectAdjacent")] + public bool AffectAdjacentTiles = true; } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs new file mode 100644 index 0000000000..f3150576dc --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Components/ThrowArtifactComponent.cs @@ -0,0 +1,27 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; + +/// +/// Throws all nearby entities backwards. +/// Also pries nearby tiles. +/// +[RegisterComponent] +public sealed class ThrowArtifactComponent : Component +{ + /// + /// How close do you have to be to get yeeted? + /// + [DataField("range")] + public float Range = 2f; + + /// + /// How likely is it that an individual tile will get pried? + /// + [DataField("tilePryChance")] + public float TilePryChance = 0.5f; + + /// + /// How strongly does stuff get thrown? + /// + [DataField("throwStrength"), ViewVariables(VVAccess.ReadWrite)] + public float ThrowStrength = 5f; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs new file mode 100644 index 0000000000..a2023a18d4 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DamageNearbyArtifactSystem.cs @@ -0,0 +1,36 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Damage; +using Robust.Shared.Random; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; + +public sealed class BreakWindowArtifactSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnActivated); + } + + private void OnActivated(EntityUid uid, DamageNearbyArtifactComponent component, ArtifactActivatedEvent args) + { + var ents = _lookup.GetEntitiesInRange(uid, component.Radius); + if (args.Activator != null) + ents.Add(args.Activator.Value); + foreach (var ent in ents) + { + if (component.Whitelist != null && !component.Whitelist.IsValid(ent)) + continue; + + if (!_random.Prob(component.DamageChance)) + return; + + _damageable.TryChangeDamage(ent, component.Damage, component.IgnoreResistances); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs index 66cd7ccc43..6328a8e43b 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/DiseaseArtifactSystem.cs @@ -1,9 +1,9 @@ +using System.Linq; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; using Content.Shared.Disease; using Content.Server.Disease; using Content.Server.Disease.Components; -using Robust.Shared.Random; using Robust.Shared.Prototypes; using Content.Shared.Interaction; @@ -15,38 +15,25 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems public sealed class DiseaseArtifactSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly DiseaseSystem _disease = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; - // TODO: YAML Serializer won't catch this. - [ViewVariables(VVAccess.ReadWrite)] - public readonly IReadOnlyList ArtifactDiseases = new[] - { - "VanAusdallsRobovirus", - "OwOnavirus", - "BleedersBite", - "Ultragigacancer", - "MemeticAmirmir", - "TongueTwister", - "AMIV" - }; - public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnNodeEntered); SubscribeLocalEvent(OnActivate); } /// /// Makes sure this artifact is assigned a disease /// - private void OnMapInit(EntityUid uid, DiseaseArtifactComponent component, MapInitEvent args) + private void OnNodeEntered(EntityUid uid, DiseaseArtifactComponent component, ArtifactNodeEnteredEvent args) { - if (component.SpawnDisease != null || ArtifactDiseases.Count == 0) return; - var diseaseName = _random.Pick(ArtifactDiseases); + if (component.SpawnDisease != null || !component.DiseasePrototypes.Any()) + return; + var diseaseName = component.DiseasePrototypes[args.RandomSeed % component.DiseasePrototypes.Count]; if (!_prototypeManager.TryIndex(diseaseName, out var disease)) { diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs new file mode 100644 index 0000000000..d034c4989c --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/FoamArtifactSystem.cs @@ -0,0 +1,42 @@ +using System.Linq; +using Content.Server.Chemistry.ReactionEffects; +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Chemistry.Components; +using Robust.Shared.Random; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; + +public sealed class FoamArtifactSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnNodeEntered); + SubscribeLocalEvent(OnActivated); + } + + private void OnNodeEntered(EntityUid uid, FoamArtifactComponent component, ArtifactNodeEnteredEvent args) + { + if (!component.Reagents.Any()) + return; + + component.SelectedReagent = component.Reagents[args.RandomSeed % component.Reagents.Count]; + } + + private void OnActivated(EntityUid uid, FoamArtifactComponent component, ArtifactActivatedEvent args) + { + if (component.SelectedReagent == null) + return; + + var sol = new Solution(); + var xform = Transform(uid); + sol.AddReagent(component.SelectedReagent, component.ReagentAmount); + + FoamAreaReactionEffect.SpawnFoam("Foam", xform.Coordinates, sol, + _random.Next(component.MinFoamAmount, component.MaxFoamAmount), component.Duration, + component.SpreadDuration, component.SpreadDuration, entityManager: EntityManager); + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs index 5ac8c76026..e24d31a113 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs @@ -2,33 +2,32 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; -using Robust.Shared.Random; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; public sealed class GasArtifactSystem : EntitySystem { [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - [Dependency] private readonly IRobustRandom _random = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnNodeEntered); SubscribeLocalEvent(OnActivate); } - private void OnMapInit(EntityUid uid, GasArtifactComponent component, MapInitEvent args) + private void OnNodeEntered(EntityUid uid, GasArtifactComponent component, ArtifactNodeEnteredEvent args) { - if (component.SpawnGas == null && component.PossibleGases.Length != 0) + if (component.SpawnGas == null && component.PossibleGases.Count != 0) { - var gas = _random.Pick(component.PossibleGases); + var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count]; component.SpawnGas = gas; } if (component.SpawnTemperature == null) { - var temp = _random.NextFloat(component.MinRandomTemperature, component.MaxRandomTemperature); + var temp = args.RandomSeed % component.MaxRandomTemperature - component.MinRandomTemperature + + component.MinRandomTemperature; component.SpawnTemperature = temp; } } @@ -38,8 +37,6 @@ public sealed class GasArtifactSystem : EntitySystem if (component.SpawnGas == null || component.SpawnTemperature == null) return; - var transform = Transform(uid); - var environment = _atmosphereSystem.GetContainingMixture(uid, false, true); if (environment == null) return; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs new file mode 100644 index 0000000000..4554ff94b2 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/KnockArtifactSystem.cs @@ -0,0 +1,24 @@ +using Content.Server.Magic.Events; +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; + +public sealed class KnockArtifactSystem : EntitySystem +{ + /// + public override void Initialize() + { + SubscribeLocalEvent(OnActivated); + } + + private void OnActivated(EntityUid uid, KnockArtifactComponent component, ArtifactActivatedEvent args) + { + var ev = new KnockSpellEvent + { + Performer = uid, + Range = component.KnockRange + }; + RaiseLocalEvent(ev); + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs new file mode 100644 index 0000000000..52d9fb0b36 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/LightFlickerArtifactSystem.cs @@ -0,0 +1,38 @@ +using Content.Server.Ghost; +using Content.Server.Light.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Robust.Shared.Random; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; + +/// +/// This handles... +/// +public sealed class LightFlickerArtifactSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly GhostSystem _ghost = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnActivated); + } + + private void OnActivated(EntityUid uid, LightFlickerArtifactComponent component, ArtifactActivatedEvent args) + { + var lights = GetEntityQuery(); + foreach (var light in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.StaticSundries )) + { + if (!lights.HasComponent(light)) + continue; + + if (!_random.Prob(component.FlickerChance)) + continue; + + _ghost.DoGhostBooEvent(light); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RadiateArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RadiateArtifactSystem.cs deleted file mode 100644 index de67aa970d..0000000000 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RadiateArtifactSystem.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Server.Radiation; -using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; -using Content.Server.Xenoarchaeology.XenoArtifacts.Events; - -namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; - -public sealed class RadiateArtifactSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnActivate); - } - - private void OnActivate(EntityUid uid, RadiateArtifactComponent component, ArtifactActivatedEvent args) - { - var transform = Transform(uid); - EntityManager.SpawnEntity(component.PulsePrototype, transform.Coordinates); - } -} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs new file mode 100644 index 0000000000..a8da6c9a51 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/RandomTeleportArtifactSystem.cs @@ -0,0 +1,30 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Popups; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; + +/// +/// This handles... +/// +public sealed class RandomTeleportArtifactSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(EntityUid uid, RandomTeleportArtifactComponent component, ArtifactActivatedEvent args) + { + var xform = Transform(uid); + _popup.PopupCoordinates(Loc.GetString("blink-artifact-popup"), xform.Coordinates, Filter.Pvs(uid), PopupType.Medium); + + xform.Coordinates = xform.Coordinates.Offset(_random.NextVector2(component.Range)); + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs new file mode 100644 index 0000000000..93b01d68da --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs @@ -0,0 +1,43 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.MobState.Components; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; + +public sealed class ShuffleArtifactSystem : EntitySystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnActivated); + } + + private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, ArtifactActivatedEvent args) + { + var mobState = GetEntityQuery(); + + List allCoords = new(); + List toShuffle = new(); + + foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.Dynamic | LookupFlags.Sundries)) + { + if (!mobState.HasComponent(ent)) + continue; + + var xform = Transform(ent); + + toShuffle.Add(xform); + allCoords.Add(xform.Coordinates); + } + + foreach (var xform in toShuffle) + { + xform.Coordinates = _random.PickAndTake(allCoords); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs index 4bc28eb063..b2cc629cac 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/SpawnArtifactSystem.cs @@ -9,25 +9,40 @@ public sealed class SpawnArtifactSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly ArtifactSystem _artifact = default!; + + public const string NodeDataSpawnAmount = "nodeDataSpawnAmount"; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnNodeEntered); SubscribeLocalEvent(OnActivate); } - private void OnMapInit(EntityUid uid, SpawnArtifactComponent component, MapInitEvent args) + private void OnNodeEntered(EntityUid uid, SpawnArtifactComponent component, ArtifactNodeEnteredEvent args) { - ChooseRandomPrototype(uid, component); + if (component.PossiblePrototypes.Count == 0) + return; + + var proto = component.PossiblePrototypes[args.RandomSeed % component.PossiblePrototypes.Count]; + component.Prototype = proto; } private void OnActivate(EntityUid uid, SpawnArtifactComponent component, ArtifactActivatedEvent args) { if (component.Prototype == null) return; - if (component.SpawnsCount >= component.MaxSpawns) + + if (!_artifact.TryGetNodeData(uid, NodeDataSpawnAmount, out int amount)) + amount = 0; + + if (amount >= component.MaxSpawns) return; + var toSpawn = component.Prototype; + if (!component.ConsistentSpawn) + toSpawn = _random.Pick(component.PossiblePrototypes); + // select spawn position near artifact var artifactCord = Transform(uid).Coordinates; var dx = _random.NextFloat(-component.Range, component.Range); @@ -35,25 +50,11 @@ public sealed class SpawnArtifactSystem : EntitySystem var spawnCord = artifactCord.Offset(new Vector2(dx, dy)); // spawn entity - var spawned = EntityManager.SpawnEntity(component.Prototype, spawnCord); - component.SpawnsCount++; + var spawned = EntityManager.SpawnEntity(toSpawn, spawnCord); + _artifact.SetNodeData(uid, NodeDataSpawnAmount, amount+1); // if there is an user - try to put spawned item in their hands // doesn't work for spawners _handsSystem.PickupOrDrop(args.Activator, spawned); } - - private void ChooseRandomPrototype(EntityUid uid, SpawnArtifactComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - if (!component.RandomPrototype) - return; - if (component.PossiblePrototypes.Count == 0) - return; - - var proto = _random.Pick(component.PossiblePrototypes); - component.Prototype = proto; - } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs index 87181dd97d..06be114d42 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TelepathicArtifactSystem.cs @@ -29,8 +29,15 @@ public sealed class TelepathicArtifactSystem : EntitySystem continue; // roll if msg should be usual or drastic - var isDrastic = _random.NextFloat() <= component.DrasticMessageProb; - var msgArr = isDrastic ? component.DrasticMessages : component.Messages; + List msgArr; + if (_random.NextFloat() <= component.DrasticMessageProb && component.DrasticMessages != null) + { + msgArr = component.DrasticMessages; + } + else + { + msgArr = component.Messages; + } // pick a random message var msgId = _random.Pick(msgArr); diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs index f124ad1caa..97c53f3ecc 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs @@ -26,7 +26,7 @@ public sealed class TemperatureArtifactSystem : EntitySystem return; UpdateTileTemperature(component, center); - if (component.EffectAdjacentTiles && transform.GridUid != null) + if (component.AffectAdjacentTiles && transform.GridUid != null) { var adjacent = _atmosphereSystem.GetAdjacentTileMixtures(transform.GridUid.Value, _transformSystem.GetGridOrMapTilePosition(uid, transform), excite: true); diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs new file mode 100644 index 0000000000..b210bb053f --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ThrowArtifactSystem.cs @@ -0,0 +1,49 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Maps; +using Content.Shared.Throwing; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; + +public sealed class ThrowArtifactSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnActivated); + } + + private void OnActivated(EntityUid uid, ThrowArtifactComponent component, ArtifactActivatedEvent args) + { + var xform = Transform(uid); + if (_map.TryGetGrid(xform.GridUid, out var grid)) + { + var tiles = grid.GetTilesIntersecting( + Box2.CenteredAround(xform.WorldPosition, (component.Range*2, component.Range))); + + foreach (var tile in tiles) + { + if (!_random.Prob(component.TilePryChance)) + continue; + + tile.PryTile(); + } + } + + var lookup = _lookup.GetEntitiesInRange(uid, component.Range, LookupFlags.Dynamic | LookupFlags.Sundries); + foreach (var ent in lookup) + { + var tempXform = Transform(ent); + + var foo = tempXform.MapPosition.Position - xform.MapPosition.Position; + _throwing.TryThrow(ent, foo*2, component.ThrowStrength, uid, 0); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactActivatedEvent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs similarity index 52% rename from Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactActivatedEvent.cs rename to Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs index 9757316427..57f5089825 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactActivatedEvent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Events/ArtifactEvents.cs @@ -12,3 +12,20 @@ public sealed class ArtifactActivatedEvent : EntityEventArgs /// public EntityUid? Activator; } + +/// +/// Force to randomize artifact triggers. +/// +public sealed class ArtifactNodeEnteredEvent : EntityEventArgs +{ + /// + /// An entity-specific seed that can be used to + /// generate random values. + /// + public readonly int RandomSeed; + + public ArtifactNodeEnteredEvent(int randomSeed) + { + RandomSeed = randomSeed; + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/RandomizeTriggerEvent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Events/RandomizeTriggerEvent.cs deleted file mode 100644 index 45ceae788b..0000000000 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Events/RandomizeTriggerEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Xenoarchaeology.XenoArtifacts.Events; - -/// -/// Force to randomize artifact triggers. -/// -public sealed class RandomizeTriggerEvent : EntityEventArgs -{ - -} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs index 74a2ad94a5..50e2b0ecb3 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/RandomArtifactSpriteSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Shared.Xenoarchaeology.XenoArtifacts; +using Robust.Server.GameObjects; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -9,6 +10,7 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IGameTiming _time = default!; + [Dependency] private readonly AppearanceSystem _appearance = default!; public override void Initialize() { @@ -29,7 +31,7 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem var timeDif = _time.CurTime - component.ActivationStart.Value; if (timeDif.Seconds >= component.ActivationTime) { - appearance.SetData(SharedArtifactsVisuals.IsActivated, false); + _appearance.SetData(appearance.Owner, SharedArtifactsVisuals.IsActivated, false, appearance); component.ActivationStart = null; } } @@ -37,19 +39,13 @@ public sealed class RandomArtifactSpriteSystem : EntitySystem private void OnMapInit(EntityUid uid, RandomArtifactSpriteComponent component, MapInitEvent args) { - if (!TryComp(uid, out AppearanceComponent? appearance)) - return; - var randomSprite = _random.Next(component.MinSprite, component.MaxSprite + 1); - appearance.SetData(SharedArtifactsVisuals.SpriteIndex, randomSprite); + _appearance.SetData(uid, SharedArtifactsVisuals.SpriteIndex, randomSprite); } private void OnActivated(EntityUid uid, RandomArtifactSpriteComponent component, ArtifactActivatedEvent args) { - if (!TryComp(uid, out AppearanceComponent? appearance)) - return; - - appearance.SetData(SharedArtifactsVisuals.IsActivated, true); + _appearance.SetData(uid, SharedArtifactsVisuals.IsActivated, true); component.ActivationStart = _time.CurTime; } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs new file mode 100644 index 0000000000..3855bdc4ac --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactAnchorTriggerComponent.cs @@ -0,0 +1,13 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers when an artifact is anchored +/// +/// +/// Not every trigger can be a winner +/// +[RegisterComponent] +public sealed class ArtifactAnchorTriggerComponent : Component +{ + +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs new file mode 100644 index 0000000000..d442d5d430 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDamageTriggerComponent.cs @@ -0,0 +1,29 @@ +using Content.Shared.Damage.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers when a certain threshold of damage of certain types is reached +/// +[RegisterComponent] +public sealed class ArtifactDamageTriggerComponent : Component +{ + /// + /// What damage types are accumulated for the trigger? + /// + [DataField("damageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List? DamageTypes; + + /// + /// What threshold has to be reached before it is activated? + /// + [DataField("damageThreshold", required: true)] + public float DamageThreshold; + + /// + /// How much damage has been accumulated on the artifact so far + /// + [ViewVariables(VVAccess.ReadWrite)] + public float AccumulatedDamage = 0; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs new file mode 100644 index 0000000000..797583a8a0 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactDeathTriggerComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers when a nearby entity dies +/// +[RegisterComponent] +public sealed class ArtifactDeathTriggerComponent : Component +{ + /// + /// How close to the death the artifact has to be for it to trigger. + /// + [DataField("range")] + public float Range = 15f; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs new file mode 100644 index 0000000000..a494b95468 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactExamineTriggerComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers when the artifact is examined. +/// +[RegisterComponent] +public sealed class ArtifactExamineTriggerComponent : Component +{ + +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs index 5338f19f88..ceba7c8461 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactGasTriggerComponent.cs @@ -12,7 +12,7 @@ public sealed class ArtifactGasTriggerComponent : Component /// List of possible activation gases to pick on startup. /// [DataField("possibleGas")] - public Gas[] PossibleGases = + public List PossibleGases = new() { Gas.Oxygen, Gas.Plasma, diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs new file mode 100644 index 0000000000..d19880e73d --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMagnetTriggerComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers when the salvage magnet is activated +/// +[RegisterComponent] +public sealed class ArtifactMagnetTriggerComponent : Component +{ + /// + /// how close to the magnet do you have to be? + /// + [DataField("range")] + public float Range = 40f; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs new file mode 100644 index 0000000000..c0c5bd9a7f --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactMusicTriggerComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers when an instrument is played nearby +/// +[RegisterComponent] +public sealed class ArtifactMusicTriggerComponent : Component +{ + /// + /// how close does the artifact have to be to the instrument to activate + /// + [DataField("range")] + public float Range = 5; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs new file mode 100644 index 0000000000..e28169b6b5 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Components/ArtifactPressureTriggerComponent.cs @@ -0,0 +1,20 @@ +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +/// +/// Triggers when a certain pressure threshold is hit +/// +[RegisterComponent] +public sealed class ArtifactPressureTriggerComponent : Component +{ + /// + /// The lower-end pressure threshold + /// + [DataField("minPressureThreshold")] + public float? MinPressureThreshold; + + /// + /// The higher-end pressure threshold + /// + [DataField("maxPressureThreshold")] + public float? MaxPressureThreshold; +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs new file mode 100644 index 0000000000..568273efbd --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactAnchorTriggerSystem.cs @@ -0,0 +1,22 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; + +public sealed class ArtifactAnchorTriggerSystem : EntitySystem +{ + [Dependency] private readonly ArtifactSystem _artifact = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnAnchorStateChanged); + } + + private void OnAnchorStateChanged(EntityUid uid, ArtifactAnchorTriggerComponent component, ref AnchorStateChangedEvent args) + { + if (args.Detaching) + return; + + _artifact.TryActivateArtifact(uid); + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs new file mode 100644 index 0000000000..aa7c70753c --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDamageTriggerSystem.cs @@ -0,0 +1,35 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; +using Content.Shared.Damage; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; + +public sealed class ArtifactDamageTriggerSystem : EntitySystem +{ + [Dependency] private readonly ArtifactSystem _artifact = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnDamageChanged); + } + + private void OnDamageChanged(EntityUid uid, ArtifactDamageTriggerComponent component, DamageChangedEvent args) + { + if (!args.DamageIncreased) + return; + + if (args.DamageDelta == null) + return; + + foreach (var (type, amount) in args.DamageDelta.DamageDict) + { + if (component.DamageTypes != null && !component.DamageTypes.Contains(type)) + continue; + + component.AccumulatedDamage += (float) amount; + } + + if (component.AccumulatedDamage >= component.DamageThreshold) + _artifact.TryActivateArtifact(uid, args.Origin); + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs new file mode 100644 index 0000000000..b3b5528244 --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactDeathTriggerSystem.cs @@ -0,0 +1,40 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; +using Content.Shared.MobState; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; + +public sealed class ArtifactDeathTriggerSystem : EntitySystem +{ + [Dependency] private readonly ArtifactSystem _artifact = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnMobStateChanged); + } + + private void OnMobStateChanged(MobStateChangedEvent ev) + { + if (ev.CurrentMobState != DamageState.Dead) + return; + + var deathXform = Transform(ev.Entity); + + var toActivate = new List(); + foreach (var (trigger, xform) in EntityQuery()) + { + if (!deathXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance)) + continue; + + if (distance > trigger.Range) + continue; + + toActivate.Add(trigger); + } + + foreach (var a in toActivate) + { + _artifact.TryActivateArtifact(a.Owner); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs index 8cef4952fc..5988997101 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs @@ -20,13 +20,18 @@ public sealed class ArtifactElectricityTriggerSystem : EntitySystem public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityManager.EntityQuery(); - foreach (var (trigger, power, artifact) in query) + List toUpdate = new(); + foreach (var (trigger, power, artifact) in EntityQuery()) { if (power.ReceivedPower <= trigger.MinPower) continue; - _artifactSystem.TryActivateArtifact(trigger.Owner, component: artifact); + toUpdate.Add(artifact); + } + + foreach (var a in toUpdate) + { + _artifactSystem.TryActivateArtifact(a.Owner, null, a); } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs new file mode 100644 index 0000000000..cbade1682e --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs @@ -0,0 +1,20 @@ +using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; +using Content.Shared.Examine; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; + +public sealed class ArtifactExamineTriggerSystem : EntitySystem +{ + [Dependency] private readonly ArtifactSystem _artifact = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnExamine); + } + + private void OnExamine(EntityUid uid, ArtifactExamineTriggerComponent component, ExaminedEvent args) + { + _artifact.TryActivateArtifact(uid); + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs index b773c71bea..9a88968923 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactGasTriggerSystem.cs @@ -2,13 +2,11 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Robust.Server.GameObjects; -using Robust.Shared.Random; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; public sealed class ArtifactGasTriggerSystem : EntitySystem { - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly ArtifactSystem _artifactSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; @@ -16,23 +14,24 @@ public sealed class ArtifactGasTriggerSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnRandomizeTrigger); + SubscribeLocalEvent(OnRandomizeTrigger); } - private void OnRandomizeTrigger(EntityUid uid, ArtifactGasTriggerComponent component, RandomizeTriggerEvent args) + private void OnRandomizeTrigger(EntityUid uid, ArtifactGasTriggerComponent component, ArtifactNodeEnteredEvent args) { - if (component.ActivationGas == null) - { - var gas = _random.Pick(component.PossibleGases); - component.ActivationGas = gas; - } + if (component.ActivationGas != null) + return; + + var gas = component.PossibleGases[args.RandomSeed % component.PossibleGases.Count]; + component.ActivationGas = gas; } public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityManager.EntityQuery(); - foreach (var (trigger, transform) in query) + + List toUpdate = new(); + foreach (var (trigger, artifact, transform) in EntityQuery()) { var uid = trigger.Owner; @@ -50,7 +49,12 @@ public sealed class ArtifactGasTriggerSystem : EntitySystem if (moles < trigger.ActivationMoles) continue; - _artifactSystem.TryActivateArtifact(trigger.Owner); + toUpdate.Add(artifact); + } + + foreach (var a in toUpdate) + { + _artifactSystem.TryActivateArtifact(a.Owner, null, a); } } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs index 6fd85cd366..e54dc9a353 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactHeatTriggerSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.Interaction; using Content.Shared.Temperature; -using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; using Robust.Server.GameObjects; @@ -25,8 +24,8 @@ public sealed class ArtifactHeatTriggerSystem : EntitySystem { base.Update(frameTime); - var query = EntityManager.EntityQuery(); - foreach (var (trigger, transform, artifact) in query) + List toUpdate = new(); + foreach (var (trigger, transform, artifact) in EntityQuery()) { var uid = trigger.Owner; var environment = _atmosphereSystem.GetTileMixture(transform.GridUid, transform.MapUid, @@ -37,7 +36,12 @@ public sealed class ArtifactHeatTriggerSystem : EntitySystem if (environment.Temperature < trigger.ActivationTemperature) continue; - _artifactSystem.TryActivateArtifact(trigger.Owner, component: artifact); + toUpdate.Add(artifact); + } + + foreach (var a in toUpdate) + { + _artifactSystem.TryActivateArtifact(a.Owner, null, a); } } @@ -61,7 +65,7 @@ public sealed class ArtifactHeatTriggerSystem : EntitySystem private bool CheckHot(EntityUid usedUid) { var hotEvent = new IsHotEvent(); - RaiseLocalEvent(usedUid, hotEvent, false); + RaiseLocalEvent(usedUid, hotEvent); return hotEvent.IsHot; } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs index 79dd85ff2b..239b674160 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.Interaction; using Content.Shared.Physics.Pull; -using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs new file mode 100644 index 0000000000..e23cb2cdbd --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMagnetTriggerSystem.cs @@ -0,0 +1,40 @@ +using Content.Server.Salvage; +using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; + +/// +/// This handles... +/// +public sealed class ArtifactMagnetTriggerSystem : EntitySystem +{ + [Dependency] private readonly ArtifactSystem _artifact = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnMagnetActivated); + } + + private void OnMagnetActivated(SalvageMagnetActivatedEvent ev) + { + var magXform = Transform(ev.Magnet); + + var toActivate = new List(); + foreach (var (artifact, xform) in EntityQuery()) + { + if (!magXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance)) + continue; + + if (distance > artifact.Range) + continue; + + toActivate.Add(artifact.Owner); + } + + foreach (var a in toActivate) + { + _artifact.TryActivateArtifact(a); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs new file mode 100644 index 0000000000..1f2718da2b --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactMusicTriggerSystem.cs @@ -0,0 +1,46 @@ +using System.Linq; +using Content.Server.Instruments; +using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; + +/// +/// This handles activating an artifact when music is playing nearby +/// +public sealed class ArtifactMusicTriggerSystem : EntitySystem +{ + [Dependency] private readonly ArtifactSystem _artifact = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var artifactQuery = EntityQuery().ToArray(); + if (!artifactQuery.Any()) + return; + + List toActivate = new(); + + //assume that there's more instruments than artifacts + foreach (var activeinstrument in EntityQuery()) + { + var instXform = Transform(activeinstrument.Owner); + + foreach (var (trigger, xform) in artifactQuery) + { + if (!instXform.Coordinates.TryDistance(EntityManager, xform.Coordinates, out var distance)) + continue; + + if (distance > trigger.Range) + continue; + + toActivate.Add(trigger.Owner); + } + } + + foreach (var a in toActivate) + { + _artifact.TryActivateArtifact(a); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs new file mode 100644 index 0000000000..aa1d40c5fb --- /dev/null +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactPressureTriggerSystem.cs @@ -0,0 +1,40 @@ +using Content.Server.Atmos.EntitySystems; +using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; +using Robust.Server.GameObjects; + +namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; + +/// +/// This handles activation upon certain pressure thresholds. +/// +public sealed class ArtifactPressureTriggerSystem : EntitySystem +{ + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly ArtifactSystem _artifactSystem = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + List toUpdate = new(); + foreach (var (trigger, artifact, transform) in EntityQuery()) + { + var uid = trigger.Owner; + var environment = _atmosphereSystem.GetTileMixture(transform.GridUid, transform.MapUid, + _transformSystem.GetGridOrMapTilePosition(uid, transform)); + + if (environment == null) + continue; + + var pressure = environment.Pressure; + if (pressure >= trigger.MaxPressureThreshold || pressure <= trigger.MinPressureThreshold) + toUpdate.Add(artifact); + } + + foreach (var a in toUpdate) + { + _artifactSystem.TryActivateArtifact(a.Owner, null, a); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs index 8600dfbcd6..d8c500c3b1 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactTimerTriggerSystem.cs @@ -8,19 +8,36 @@ public sealed class ArtifactTimerTriggerSystem : EntitySystem [Dependency] private readonly IGameTiming _time = default!; [Dependency] private readonly ArtifactSystem _artifactSystem = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + } + + private void OnStartup(EntityUid uid, ArtifactTimerTriggerComponent component, ComponentStartup args) + { + component.LastActivation = _time.CurTime; + } + public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityManager.EntityQuery(); - foreach (var (trigger, artifact) in query) + List toUpdate = new(); + foreach (var (trigger, artifact) in EntityQuery()) { var timeDif = _time.CurTime - trigger.LastActivation; if (timeDif <= trigger.ActivationRate) continue; - _artifactSystem.TryActivateArtifact(trigger.Owner, component: artifact); + toUpdate.Add(artifact); trigger.LastActivation = _time.CurTime; } + + foreach (var a in toUpdate) + { + _artifactSystem.TryActivateArtifact(a.Owner, null, a); + } } } diff --git a/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs new file mode 100644 index 0000000000..2adb0391ac --- /dev/null +++ b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs @@ -0,0 +1,78 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Xenoarchaeology.Equipment; + +[Serializable, NetSerializable] +public enum ArtifactAnalzyerUiKey : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class AnalysisConsoleServerSelectionMessage : BoundUserInterfaceMessage +{ +} + +[Serializable, NetSerializable] +public sealed class AnalysisConsoleScanButtonPressedMessage : BoundUserInterfaceMessage +{ +} + +[Serializable, NetSerializable] +public sealed class AnalysisConsoleDestroyButtonPressedMessage : BoundUserInterfaceMessage +{ +} + +[Serializable, NetSerializable] +public sealed class AnalysisConsoleScanUpdateState : BoundUserInterfaceState +{ + public EntityUid? Artifact; + + public bool AnalyzerConnected; + + public bool ServerConnected; + + public bool CanScan; + + public int? Id; + + public int? Depth; + + public int? Edges; + + public bool? Triggered; + + public string? EffectProto; + + public string? TriggerProto; + + public float? Completion; + + public bool Scanning; + + public TimeSpan TimeRemaining; + + public TimeSpan TotalTime; + + public AnalysisConsoleScanUpdateState(EntityUid? artifact, bool analyzerConnected, bool serverConnected, bool canScan, + int? id, int? depth, int? edges, bool? triggered, string? effectProto, string? triggerProto, float? completion, + bool scanning, TimeSpan timeRemaining, TimeSpan totalTime) + { + Artifact = artifact; + AnalyzerConnected = analyzerConnected; + ServerConnected = serverConnected; + CanScan = canScan; + + Id = id; + Depth = depth; + Edges = edges; + Triggered = triggered; + EffectProto = effectProto; + TriggerProto = triggerProto; + Completion = completion; + + Scanning = scanning; + TimeRemaining = timeRemaining; + TotalTime = totalTime; + } +} diff --git a/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs new file mode 100644 index 0000000000..2b42cff916 --- /dev/null +++ b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactEffectPrototype.cs @@ -0,0 +1,36 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Xenoarchaeology.XenoArtifacts; + +/// +/// This is a prototype for... +/// +[Prototype("artifactEffect")] +[DataDefinition] +public sealed class ArtifactEffectPrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; } = default!; + + /// + /// Components that are added to the artifact when the specfic effect is active. + /// These are removed after the node is exited and the effect is changed. + /// + [DataField("components", serverOnly: true)] + public EntityPrototype.ComponentRegistry Components = new(); + + /// + /// Components that are permanently added to an entity when the effect's node is entered. + /// + [DataField("permanentComponents")] + public EntityPrototype.ComponentRegistry PermanentComponents = new(); + + //TODO: make this a list so we can have multiple target depths + [DataField("targetDepth")] + public int TargetDepth = 0; + + [DataField("effectHint")] + public string? EffectHint; +} diff --git a/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs new file mode 100644 index 0000000000..c00fde2abb --- /dev/null +++ b/Content.Shared/Xenoarchaeology/XenoArtifacts/ArtifactTriggerPrototype.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Xenoarchaeology.XenoArtifacts; + +/// +/// This is a prototype for... +/// +[Prototype("artifactTrigger")] +[DataDefinition] +public sealed class ArtifactTriggerPrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; } = default!; + + [DataField("components", serverOnly: true)] + public EntityPrototype.ComponentRegistry Components = new(); + + [DataField("targetDepth")] + public int TargetDepth = 0; + + [DataField("triggerHint")] + public string? TriggerHint; +} diff --git a/Resources/Audio/Effects/Lightning/lightningbolt.ogg b/Resources/Audio/Effects/Lightning/lightningbolt.ogg new file mode 100644 index 0000000000000000000000000000000000000000..df3145a08aec7887541d30490869b2ff473e2cfb GIT binary patch literal 34844 zcmagF1z26bvNyao?ogcKE$;3v#kD~3;_mLn-QA13yHniV-HH?~#fp`0)Bid5p6|Zz zbKg86WMz_>Wac;7lkBw>Oie!m5Wv5Zg8V;~?os?T5E015*1^Es@wEx$+y1XEuAsk_ z7Lda0$p1>OBVQ?p=Fuf+yf6R1Gy?e#BU&(B-Q3QMLBYY4#LC=2?Js*0DH0YYW)>zE zCRP$E1#25~I|pM&V;d*xSKc7#e}+paFm$88u3Ri4=8V zVlI_?YNF(8ZGb5(H8E@e*D#WK@ZSXqn-LZOKmoo~@X`4PQWlfkrnr>R4msTB@?6Cv zu;EJE-0;0O6oxiM4MmnVlhovJ$QsZ9ST917q(PXlKU|8Uj_W%b&!DP zA_9YFgeC&_%0863DAfVX>Yrti0KVX50uphUQni?4wFDD0WD39NlrY$5Sj9i8$f<&p zv(6_M<0Tj8B^Pgv)F7=EZ;h59t>qw{yC8ks;D6=c-dlIC`R~$6KtRrW?&Lk{ymxQ& zcDeH)1A{9d0Harxkif^kqmIv)Dl)OCGS6r-D{rzMtD_sML;BkVY*VP$*#J4_=|%rX zHql5n{(om7vtb%Q2&~H?dx9Z*GI3?HAqP6xzZxC}z%~_9rW)E2ana5PCqDV z#RS*F5;G_pTUGwy_b*tK>Sj*%aPNRegso;+w!-UxYxhQ(=M(U+ZT}NKLSVb;FXIko z`IC4@!x&(a7Lg_pImMtPN>P*ldFEOhe!yK)i$2t@mf;(r%^q5Kbu z3*y6RMycw@S;pvIi;BKUo|BqBG*KjGFpBBe!6=SuJjiye;+C{5Y+Tf&ElgCFrY`)) zqrgTbF^Uz2!TakZ!Kn_T)Wu-0_)o+AB%69eIP)KQ>XJYSogAEJSm-~p@~f(-esZza zNO4|k4$@h6-dghBT8jFN6Z}7c^OR-sOsZFBMOw5l`WsY#NqcnwI^eG!Nl_c#c_k zacX#RX!v1hG*wu#d02W`Yo66;)k)j`Gyj+8NH~yzGuU&)9mxK}bG|VXh=RSTj!y3C zpEdH&fEDT_j`N=e0Dy0?NHTx-5hZ2%1!eXHWqMT={{P)$z|;jcxj8nlVv_&>4*=eS zjU0#;?hq?t!>n3BIGp1Tj}YFn8KOH4K(ikvf= z1i80JTWX&R4!rF_0ZIVi%RJ27kLxfgHIgGX$qb*BILu91m@q3vO`nX*N=;sbZn&43 zKPxp-kccZ2o>hQu7*+`$1i*s-5Ob$vu|PtY0APTmj6fZfs60u15UV`OyicGk&0U=N zagrO3FmRL^CMy7qx{%=GB=rzM;JEO9X27&C6#$TY1A+fUsMw6e0W?Zr+m381flUUJ zTn0mY3e$KALvAUFZGw$mNkw%GLv=w#bq!liwTWB~Lr(5mMHNF_6-#w4Sx&XldWy|> zMMZT3TXmtycn#a)UP|`;n15Mus4g|JD>b*Hb?w#t)1`;#^%uOjNP zqVnUm(zZ66lQ#SDCToKHvg0nAvEG`|x{H%G#s+6nB~|B*M4i1);B{DkcFoqh3#zl` z?XG+r1v8la%{^sH&V=hM*AOMMS!=08>VqUTqu<rz5e4X4Qq+T(AFb&J)87x0fLV>a3jZr(KQ$4|DlH&hTDmeRUU@t-;gwZDw6qj; zQ@pe+cvjNVwL!|7R?Lf;%2w>)S%F!_OIy(oYHG~95=f%HQ zZeT$_xM)!MDx9V^DIP5RU{y1cJ8{+k%qm_v5$y-@@2I=+yAkT1M(&g&NP>bAn6%9B#&Xb$7tH1Hzh+?1sbvLe*fs^h|N zsh^R)%$k9J82fSxO0Pe4QrZhKf zZsG^(L6XFHWw1E#l1d$rz`7eydnF`lo|1r}Va4u(28Pw3rl@q~fVyQBY2l(l1^W^h z#El2(meOE?BHIVq?9F(~5G;BtH7vW0+R%0Y{Fs4B`^I@ha9Q8mdIL0Z$ zeGNUpAF{9UMPCPCUSRRnB)F*GBGSFC5e)?#==G(7 zXADCa7)*Us5C}F;uBbFO6*(9s)YM@3fIT`%l-7Y>S*% zw2+EPgHiR05z;`g8UAStjQy`RG@iqMpallh;XnNn6-E9+3oaMjMokC?8P(q!Ik*P@ zPXPB9jDhIjP%aV7`Jc8#bg+!S;a#HgDfahX>8rZ_b|(F2>}xCthA8Cg zF8uz8EPwQ!728fPq@vldG~kN_0QSokC)A6>Bm_s!3DD`Oi93G`fs~Pn0BZ023N1tDg`E6L%OaC=iMo z$f@}@&8!Ut^&OoB0SRF!&~err5*OR%V8Ub-6F!_$|MnmU1zdd{PWPE3bwLDTUP77x1U%;+m@NnITl(vt-{& z`tm67G`)E;rT+==vKhmT*hZ72xGk2PPNMclE;!&H2`1cnGdX`C_1qry30qW`5J z8q*VI`GNr27NU-O17IS-HcRE^Tk`bYVUOUyjeRrOr!FUMt=xKJ znZbdeN6Z{cX1ptZ1&1r=;p6(S=vJL-=yFdPt}1Ms0OO4JpVCKLH8X~M%m(RX-nPK~ zHvw9tO<&vLl5b11uyM1$#S0$Gm~Aa=wEVt{W!)*Un$G@Ee})2+#0?cDkYIb%@JTDc z;wgzD6|{#}wQYAg#?b#ZWt7p&{_6?vVDpjdDq+sN)u)UKsa!}Mx5!kNu{YS-bveR< z8fsJ|vttt0umIcDGG;sO0s|<$+o?75y)>C4tu=&~?wCF#8m`{)8d}xmqmK*E(7{3O zs_~k>#tQ|jvMn}hW)a>D2T-9KK>`3~O~efM-zK{|~Iq}0wdITo<`n@QuE4MDh81?z2x0=GHUD*bc9FpvQ*XYIbEq4V;9mW(~K(kEwu+$@-(#>~h(zcZe2#8o; zHDK!G%!mjhE~;=}n715)29gQ5wgAE`mMNdMJsbx&&xnK4cUI3KiPIFm;J2bWGV~jz zt6XZ|ioolZ_XdQUl^-N-OR}4FYm!(-JZ=H` zdsDJneFRZPlBM+lngiWsKK&=62=+MGd11*&Jl~U$x|8T= zX9zVRh2kFHc+2{D0jb%`p_Q%mqv^}q(;4MX z;3+*_D;1lP0g6aJIj;E5QyJKV<$ReP>FvUBVP;(yjF$8bJGDS6HkX!mO}U_=WA}xR zGm;tY?qKlem~|JjdT7q`p6He5eVkaaKyhrg*Pop5J@*I2tdSgN`MyQ2rq@d*t1~dQ z(RVcxaNdsF$$?ocAqdCr%FXAL@t+xMAHwHetF-Qfn3ks{P^)a5G<^7dR{!#)FXWuN z4RZ={GCCswR*hLzXis-RDz|rf@{P84>C%( zt>r)Z`>X(DxO3E0z5!d*#3`{2ZW`r>bNcS(nOQFV(=Cf%HM>}R9Ej#IStMM1yjY0+ zX=uxyMxTpG4QZq5*guNJm;W(H;jIhS;qJR%(ucyN8OzP4G*_Z#W-&(Y7k|%P(_#XZ zHGARoTX6wF=XQsWkCb+<70G^L8j#hb?)0veF6+^I=7K6PtXH5DG~}M6aeqVXA?&#n zEiu$!2KoHe#|Yi_J^H~0ZlNrMQMfA;Ne;?P#V;UrwrmO|t#!Shib(nNiqK8&s|9oL z7nkd*U$xHNyVkt2>UwaW%!{J|G0kcog%YM3H)9Cug_ttXpB3p`?JkHd`X;z7pWM+U z-)Nf8eNUH>!joU-jt|I<-kOifSITOq!h2J(fj%jCnMyKT85tVQ>W-W|yFPk^-S)Qk z??(>dZ9H;Fe#6>FuAu^~bj`a>O%g05K|957t=?K~|DKk-&cv7Nxm|HyA35ue4%oKW zzk9hx9Kd+_9Nhil3+3b@gEkdgVZ!jh!WMh%iGNleU#{=mByV`wIdb*9$A;XL#JUWZ zku*`E&aV)qK^|NMnoXYokdC0NMSfh*T7He$<>;(42MoE^O%Pi%Gu3;io*+*ZD$?JZ z&IuM*7kwt05_FYO`2O{KRAx|9fB0O#MaFoAc`j|1?#&r}389?oM6(L^r`wcWj;%@v z$tVcs*^Ps!SOGgfm>PO3wtF#RAJEF^vlrg@8@3N9aeCUf!-lP?B`1^ZYDd$v6_$<@ zj5T6dTNv znO=q-@BD^=fzqJfIU$)2WJo|SSt&X2?Li!Lo#qyTD6Nk2%}EKa63IFfJOZH=&4FXgD^lLeCM z))+#{vJ()8>y;S-u@KT*fKT%q}h6SFoT( zmA>Eq^trLsV6y(9s7bJv>CyRpF_&(XhqPA_{5-2tU=%|Knb}A<*pPwoi@|CakcSR_Xy-8DUkjhfvC6f~}V2$xfx@Z^h7d>9% zTu%GF7T8V*Ib(L8VI3Z_;g}SC=JO}yr{n%2g*4xCgyyF8Lsfz%E!nr^0yjme;jcpx ze{vza=uEO9-2l#~rMHd|wBWT>a>R`@SkTLS3CR{5dvR|x9nap<*M0u-I9A~PqamKd zSy3i?naq{*R)GO;&OVPk{Lu>cQH`Q|_V|rP8JdQUUf6xFcLr$A-Fkiq)feW5Z9$1Fi$8=_;ScTJr0^HqI@%L%X`e1F38Qd>^tH_Ea$Gc01!gF6<} zw>KQ+>c%vY@UhU&vkfk_N21Jr({3426w=?gl|xEI?Ppq3p^%cNc{*lq@gDA{yr=fJ zY6yjVHU9;;xJssouI~47VhRptC)vE`1VKK)a`kDZ&L`w^oBl%r%R#WZ`71UVZ3(^Ux*peeDOw^9^^iOUF^ zPbq5L3`q~Y>xy`pW`>?;L9m#~xvPda&YD2}9({ubC+|Cpf;PR^f*-q;5m~-A_;AK~ znEa7;s+%Eh%rb)O7ZDS(Nl6q{3-IJG4!)7#08;2+t$zEJTZbRy>vS>r=sfQp@|#_3 zpV;^dVy1zh%7q;<67f;GKZ4M*yOJ4U6>N4d1aj#+emwL6vf#SOFn~={h=-W>S58uO_SQ0ST)PVfmL-oTSYeHXvIScB>8x1zs0tP~__waPY? zpkcz-yx$W%nsWYoeUCHA2udD*Pr-cPWx~ zMHg7lroI9A9|Cs-NJJ~()xBMWYL^-uq}va@Uel8sc=X1s>M4ZLVvaRa!ycBref*dljm2lD! z;7IoOrv~^4)RvLl(Id?V?3|J@3z^9AAj=ee(tKj6B06mU89d4M`x~y_ z`v8STA>2x)N;Ij{P5cKnn6k8Tk)?gsPOb^qeU>G=Mb6oCG$k)1@05%cEl_vdI((hj zs6m$)H|?N-eg4Id;X5Vvl*#R`!)YSNfpp}~6%7}3}jBcwwLg~o= z+&l>RfISeLUT+>C!T;Yz)n*ZrQ&O|C9M0_TtnF^>Z*HG#F0W8iP*Ky;GBd1BfDH}@ z;DkC-;^0EE#@W~~@7n+LNb8*v))l|_VaqIA`IKwULXbiVC7q7gk*4Aw`u%5w5?wjv z_wgSj^=6T=<-dK3D9n4as<9P;^>BVP0Go=}_kA78c7G+kPLsXzWIwjriZ!>fK*T$w zVTvL22B>0305d@eplRvR=|%t`5C|SCq0>E>Lzd+!(YsbQzhoWOpy7n=vDC|(xtC8o z*>n}gl+yyIdE_u-3=EkTU66@}kFbhl$yxO69wv$0j455!H?!`7v~N+3<62X^8h=6- z3f1;ILV8$!fRgkF^nAFVT+tb4_?KoRVP%Kn=ak?`AxP673*VgICO-XHJLp~sDEKjI zci}0IuRX>!9z;F>)p+%zH8nJHrffxq=Wr5sqeQNq-r*p|UdU3>(9&~sW76hmpR#vW zFjS(@T&z*X88JmSLg?kUuH4C*=QC&eI#+=+`=UchJ+d*;;Ag4kFYYQDJYy5kS|<@T|W`Gj~(G)#B4m9S+Jcm4avAZwZWX$#Js-^px~%|%0)Db=b; z<3R>YLlKbyo!SMu78&Vv$VUCHeB8g@!BQ4)0J>HtzA$jWLGwUXzCNU+o~G)KLvRp* zdCY<8T#ooy28E06X8$KnMve>$0SeopADe{9BhDH0-w!*;2Ldaxs?v)ZDw+~>j>5^~ z$-ntsT0{0>85D6&9=290z`=`Xc#xpupglCh_n25pib2)CV3mEk`ob_G zWy~=$5eem*-n;6J^1=VaC!`O@%!0+8tGaI|I2i@l%*2)tEF#fEo^mGZfxuu|Y_NFS znCuNrvHq!Q`E>c`S=#>SS_mV5c_#7GCF=&Ry1YKk9Wx|pxMpIDQ@yG??3Tw@{KocX zozj3MW5PB!429}>_r=EB%!>N*uc^C<9!^ZSob^)Fhf1cGlO0Zq3PaR&IS+vv70@@k z$K2SQd@4Qe=ehz&O6Hr_%cyw$Nv_mJs!};iNkI*EKZdE z0h&fh?ut!R2p50Md2@S)$>(n#b2f#IJDSv^AXq(BYrb&scCu!+dyCQMVgGmH7)E@ zdgW;cey?OC3tuB#N2`Wv6^Ny^w>Ti=$>J#dU2>)dI`N@K|9X7KE_n3AS_N0n$dg-O z(hFf>QuuZ-zFTv_F=qmAbYI6iyZ5}CN3K&_J&IWX@V(8BQ8lA(Z9I|qtnIM1BC4Et z?uZCsKlN}&xWlq#8olcEmB+`+1PR3F3%b~VU_&NCBP#E$L(eSr`7m&Z@Tf(hgLc;;ylqmnA#7Up7y?35&1#Lj^K}297nb~xQ1x=9ozM89 zu2e&YKGf!-AwQBaS@uR_)EOo2%JL&#&OVcg7EBjXvOiRxDCEx#uEVfaSX@qD>0bT; zE3Txj7uebL(Ytz`DcRO1C+nfJp$6+e4KhT8SJE_ZRQv8aBpiJtRi#c9FT8sZ%6mo* zlUF4XF0T2tWeeICL!%3ls#eIRSRG-!d(msWh?@6|#Abts3sFZv7Jr;NP`->io%1*O znCCKg5!aq0Nb3aCIaUF>*VCRw-_7P={I#1kiudEQ&};H}yDCe}4tp=($q+y66^> zgf1bzHLqi7JOWopg`cCEo4(~oDHpFmaMZGav@LR z8RpL@hv%VA-K<|5;8pJ3I;BpR&fwEE=zo6;&obfOf_vvPLXy<9>;aw+-J&nh0^06Iu1xZ~ImOhgs4bhQRn9B8uoY|(kZDbJJ$j%G_-sSG z!TG$(W6N;wAIDDPiGW?ruVqAJ9YHSh;Qr+|Xi^rxuSO+o)ya_;yFpsw{FC%&gguSr zqbjaT&)~LNDz#|uy$=v8Ge6(nb*-nkBy17xVk=;Z6(zw|WyIxG9GxU=#U3S*GORa76A^yEH z#pFeVoJOgjLMPXrV#~~NqN6n$*A?>w-z?kC_-i~F(50R15A4Feb!)bhLE0L2Ky9#Y zp%okoXA}Q@(v13bvMHP2t_|-ghJSBPj^}A!D>@^g_Io-rcmABX%*~V%*Rg7&MF;H~ zPc5$D!6$p-Yv-T8h*sLW!oCsRS*~;-rcv{ssaeV1Vu~7Eaj74DA)U3xyCo!z#InUQ zOF(Nq{{YE(?$fTsUr=zIScJ6RrEE2_wTaaKtLp51x4A|bHc4P3iW5!SLB}Oc(K0RC z^EGpeHEhzTP4Fe}xJxt9*`-8}dZ@*qikBa-Q#N|j zzhA}=dF!Taf@2;s@X9c9r(zX88UBm)z(KtIlNxV4_Vda!4`ZyVMaNw01kP8LNJ$AR2D_o3K5uz|KVuLw!>pXD7o3=(P zPnJidsNX)HPmGD&ha$6XeNjJ+GZy+d`b7(A@Ai}^iL?w6YtF+#mkR_9A%m)WqU#e!yqkyBWMMaWR?LXk7mC zt1NFvj3RG|L+UC8gpq z&hNySiXO&xwC8o+EPkiBBFz550c|#ts_W-h7;*MyUX{zrjmJ_GP=1VV!{KMF~3 zXUG2dJ1O4GJ-RBK+f0pa=YgO%?FcnwSVL(k*nZttsCzt*Dv>FM^%cFMH}}#to|oOLfBI;K%01qW3%RdjiEB-rf1}j z`{Nu2kFhQSD@z4IZz9HTFAJX+DUb)Ygv-|}P0s8ybw^^xU(ROd$u)U&$*CgUn!{qs)Jxa+`GT{MG=<)_9KpWCtH4y&kf~ z@YPY>kq9OSpy!dhKuVI|8;zsW;ToIOTyr^HU|bgIxYSnBvm>tLk;c#eF5UlGE{aV4 za85_Fr+DkrLD(YD#9QPGN3xB4^2kLzbvSc{(n-$?GDKG`)EE&<@pqeFzba!mPj{ez zecNcq(de`P>z*=0jyr8O*OX8?i{F0Sa%5nBbmRkT@CBh#P(B$F{_MvOEsLijc{Dhb z))oQynLdJu`lM^MMU0)ALbT_b7S(d$3Xy0up}l@;&VL$Y7bx#OqpA8PfnS&{GB5k8 z=1EleF;rK;wjmM%9_gjnNW^ks&oS}x`sR?vHhF`(a?YS6FRfA?W>BXa=EJYIMESK* zvVI`-D0-i9pY(eX4Z~5__|AUd6s95F8_9?PrGcp#5J5**Wc~3m$tIt!))II59_>#g zmL(meV!MAy=>z0-5i}%E%eLkf$~SVs#`0Q~Pj*{D!yH!#Yr5CIyVmuO9CL7S0A@-q z1Wg8s%32Jz9l<(^{DRZsBgcD2;+7W6Dp&W7)Z?C8!rDA$5Lvl9AU!Y7Gqok?J1>0g ztHhZilXzh%5~^#nwawwMnJa82;(D>u4n8@R3Qby}a7(T4oVo+$@55Gt=4*D}+ohkE z%*>rAOK(;O;^?RoD>0sii1Gn?VN1z7j$$42lhNf9CfMr}@3W#JZv))|%6)(@)zR5nI!p_fiw0he_ab(scK> z1L$dnIBmm$bU+hqIfv1EUt2A5Vx4axZLc)&t(k z7V*G9U~c~|+P^A__E=m?(}zk`X)%!X`YbUxb~ko&a(M7~eSLW-H_v$Z^I7M<=f}iV zkBE!5r`AfTdr*dT^x*CA-ch?sNeMB@)4A$~z_BNbVAdU(I!ny*XErGdsAErw#rauN zpEqfY2_#N*Pq&sOaKFS+dOE(r%3=r$uonE@Lbd!ZLqQu@!1bxgHmX*R15yy{)Fgkp zGnM_%F+DWV2p1>U)^!Tydz~xZtA-Ki!$^fi=M%s3ZEKeN5Ob6siA5mh!Ssnf|8n^@!M!mZ)aC$Z)Zjfv_fo6;SLAtQH%>TZh5>K3SPb_W|)!o>K3Jh@}CvAwY!Y^drHA8$1VY{ouZYC;_UCy7T z%!Fv^_@D1O=T)4JYm*=?ef2G|2?p zA#@7&CR0>9h>Z5#vOz;AvL_Sdqh}ZWCO+44=eHc;rakZYQQz(6ytrkb)Qqd(s`D!( z*6~vOhlrq&58JxdWXg|Gd?8~07)VTta$)=WTH+apfO)2q`rdwdwB}^x7#JO$-Y$6_ zBj!>_%e-Hxu|opBcD|?76_RO?m5^nXI&`?!B5^oL%3Lsb#jz3()!8|T?RlPlre$xc z0-|=Iqx&q|rpq3OhXDxM`^1K! z1b(-qq4~ZC?YKuw7TO!9UB>Q+9TtbbxW*qyvkNi;;BFI0P>j{X9NcSDWZR>9ckfI_ z)fVMtXr+DXs&l4BE#ZG%prDs}0%Yx1d5TIe1=;t1czs!Z;xIH3vMWet20saFf8ELZ zh3kKl5LTR!<>z^(N(MkRS1Y8)9XdG}tb_R6mI=Rg2K~CGn*Z49Lp+joVB8&k{G{GA znKmriIs~=t0eE;-IZq4{vf*2VUp%Ewiql(*(I>zf;>(Yc4N@Z*Ex39f?{)Vv%CRfG zAmW_39bR0o!6c{kbV6PB9}a^~(@5J4J0fMjdM-Zj|7t%8GlmeZFFDuyuAPD*{^xP6 zT7=*iEYV`Y{vZffw(j$CZ!b0GuJvj|o86z0X+nD|Tu_~Bhi70|LmXzkDo9J}B(Pg{ z0zR2?O|qHTw(a{d6V#lf2TkmIX_iVvf+xzzSR_O3B^ts>%$k@z0s4B|EZ9xHa}Sg_ z68P!wYVJa^W0>$Na)apZI>`_*x12urqbH0o-dvCFwolK$q<)2O3^mj2#le6BS^$IQ8o;PDef4?(Mt54OPWwq+{`JkFrrM!;o zGfvD(%3OEa1=?>EZh}%`sZpu`LAUw@%e))iP7fSP9LIDtCrNYW@A6R=LUJPzRRcLh zJ<{7q4a+$loj-EbgtS|5oYA+pZbH*{aRVF`(_EUjb@!erlMcGtA$7HFa-e5?0lCey zBfSMn5J}RU)O!NFx>=A!k1cDs`!ClkH`ecEeG_Ac?LF&TTAY+|1*&(Bifgu&MlqgK z>upOOJSYHyc|D%$XPMoWjW9j_7#+vVB)%)Gy@{ZkB>QMoh8|oCCMS1f92-Bx8@k=+ zEgoe*>V$WNL>pd;m$a;P4TsJ;xaSweUR&@=xNR)=5-mFVB;HpgoXz&p?ICnkrgOl> zJXv3LxWb`jCFl0r0KCMNvU*I*5~?8aBE(Gso3@bF94f{49*%H<%@8F>LaOnBk>j(V z7-HqK7lGCngYzXvMuF#_e1(7Hwi5cM^0l;eKC#0D-Dc^%0ez0Z=hn(AUM%y2T8KMP z918rteqgM##7#I|%%F6+N*$~xz}9pn-&|jYgPo#sCsrdUIXkeQ(Qlx3Fz$5Jqvvna zvj;zlMM8@4L+nA*kWhS$dl^`D;r1qfV?jWFKs6@zZr`=uo|X5E0+rMVtFbQmv&E_V z^w8s+Oy#w0UpxjX(B)<0fWit|6O$WZ#Z;`%V37fm(C5x%;RPWj&(`fm68ZFFO zE(IkXqdE;mFCtJK(FHp^7ZnKwEV*!&Dc~?1ODLVveNr||{kjAYIp0&3OMEtKSWd~2 ze!o#Oi|a1$9Dhch3CH1{=4O;jwZxnBGw$7WnGs4K<&q>yXoeH_Wc5yYsR(50x8ki$ zGf+~ln6V7EIK=KrldTA*EP*1C`G(5CPV*dB9g=j@r;p<87#$142s=tg_&^ukd0P;N z60!m!d(l$5z&8Wf)B^L7uym}|;YM??+Wv4BULdpyq-X!=~gfWCrbM29bRMvug#2%T(F}l^QyoD<@p*ZAr@vKKV zA04H5wU;ueG%JyAP&3GgJ7`fv4RlS(J}>b9&(!q4Q2@rM|`Smv*>5l;Mo@j;5Vip<2ov@M}-EpPmC45;oDyroUd z6Iy@qxZ+(KH#&GM1qZSyjYBUCTBtg**`t#zY0B{&X(x4uvQi9gs_G^vu!=1*0@2Uv zT@&fWw-RB#(2mmVx%T;@#jQNCai?F|GAp{>FoGB3h9INqWxFWT&0#yTd3-7h`*@Vvx&`?l+ez9Pg2P$+rR`uww}enbwT;a9`=*@AIQbt0Zb_xmy8n8#`_=V>>G zn2C^0h2gHcyGbM`L8k9K8pUMpVY_yE?3lBj3%Y%*<{~9ThBGNSBpO zUXa;0d<*I9j}`Ap9%2F2gnq_C3~|3q@B!t}&?Na7Esi72DlA+X zhxma+?jWORzar{f!aDiP3V(dqL%q{~2a^A$tfHsaFiL5DV%o-Sq{KL?qfaxRXFB~t zM&@A7oU*l(nS}LAcX*LO0c;#!whR zZFje%zu9#gQnZ9$T{W%|^Hq!Mv!?*ckzBWNiIhERF;Xyj_9|=XS@^JI8TZbN4-0&&>70$U5N?1cd)>M&unm~Lrf`mih+LrMu61CBt}^C(I(%9%6r81NFMg@& zwiTS>KiDlei^1*@RWmLwU2@>ijb~1K$V1w~+;oNu4V$j2A-lW|;h>!EA3P|K8IdKj z%rb&~&mmdZQrn0BHj7_CuM6HUTce9(A@!{id}X*5!Jq1kMu;of6Y@iU?##-Pm0^0I zTpZon0e4ls)7Ea>wn1Nf+yc{jd}&}8+v8XE2irLz>UBaGs&g8ZjH`lO3}ZY#pY_Uj zK`l9n5OUFp#)!VseYM({amYVYf7>qBk62B)wf{c6Vz@fpL|qCnY>A6pZ^L~!*L4o8 z-S|-F^!VnnG>;)i3Qt$tCG z0gEZRup4bw(H>$?goWG;KKJZ&mD*cn&--HBLjx7zR;i+OEu$tJDBZg@-!BaQ!Ivmy zcD5p4KLB($M-Z2d!26~XTxXCqa$4P8U`1*ebH6-^8@^%u;l zXBlrPlkhvX(A9qCe){}OBCEP_P~P;@>_BHe%otVm;YG5uhu`c7K~SZ%TD@VUskbmh0R zxpGdw=?;^-Tf8@UY=3n4q`^qg8NnrY{ZYp+L@#)DOn&t8xXZygU;o6P_bu&yb4x5# z@f{{`t*%~PYJ(>*6V8mH!4R$C@9mUj4^De@tC8Pn&hRGf@f zEt($kNBP957%{t#i(Qes7_7gW&|_gJS`1<@E*G5l(c3?IW&IW@@Z$%2Y}Q0-1*hbn z91{naMCfWr#f34~D0U^K?WgDU%9Llf^yb5u3{#s<&oLsVmor@8HWx)5#AHs}+ShSa=8W$Ggfqb09CKbb>44uc_uLIMO{a)7ss_ z5e`;I*QjF^Rbm`_p$mL!hIN(O>KZPPLYwS9&jw&B(bzD}|4O39+t$*nKhKIys&Gj0 zC}g0DQhy#x_)!QYA+||Y_jMJ3A2yESJqvVyp#}bEZyqxzg0dBt`tbAj6E z5>URGW-!j79>;`qr^2Uv?S5|IyT3Nchh^AIyIpWoBc(B_Pv)2^FpdqJOyA*svtBCK z?8^Kly6Kr{#>ZNMqOD{o8DG#0*SMJ7ddIh9z_Lk0+XO`uY^l!Se6(nhMqI zEeVbDy@F)I_p`nJ{_#->|w^5*ByV7R9W7y{H_DP zcIldV?;MUKux_0f!VQ8xChD|el=Ky7WgH|cMu)L8UF$qEy|B@dkUgFok^dTsM744}CM07;FY`&Al zGmAU8`Q0SY+S%0u*Fw1b?v6Gq8;e8Nmlr|sE$!-R<$dyw`y_oHhu;2KRt<84y^+uI z?*IkLx3yXXI&^^UI(XLJpwIp6vC+tT-rf=+TX$G|S%g3kv-nym@fy+>%e4y}p@ zh2IZ#Ab7cK0`~)*c%U54q+7yJxin;o|2WGuA*TOkxn4yA2dvo&DP+Ct*j4u6j|>dT znkj!lZIE$+zTAzGU?#VND72rLh=q4L4J$z>-aubvlUg>@zpo>}zwdiZuWvNGzFt)Q zJD3=+s;;TAxxTutwxTLAIwzy7I4dbRHYqJ5+vkaJAAJS#=a$IX<#*f9in4mwGLNOZ zW}x4<b~a=h)$kE+kq z`q8Q88*R}sM-FKO5INi3F7FNXrw}>XQS-~6Z6;kA;SO@B?MLL^Q3-+=p?#TwY`X0J zx5=Ubb&B|#$F;H?^u7%|pKTJEFuw)8Sfz+jY!QFyj@9CCKYM;UnLG3hZnQo<^T)gD zBItQ+Ift~UPVnOrw_=BS!_uv7XADhow2rgRfIqf=1~JuD|7N8ZdZ{(@IKMx%@0Pb6 zv}81!n?0jrby*gfcn3u-+|xszE(w@fgn0@Qa{v5&ZznhilX|L(sHB#w5l~UmL5qAH zjeVo+*@^a3=T1w9xsD6}kO8DCo4rF+1pE;_`)Lho(*U7Tn7@5i8L0q_B3k`7Z_j6( zD!to@vYwNBLdai2*hFf$WfIQ@3E;kLEIOc4z+=PTx+UbVA4ENR*c?v|*a-zb`t_S1 zw-Tv-Y~YVJkbgqZEpt`7_j^}$i7kop=RWFGnUU?2aFBWM!B&!oROnOJmoUB-X7d;V zQc0*pR9VY+`Xp;t5MP6!Vq=oVZBtDmzMEmZW27;CWAeyW<*QN2J!JL%9pv(#YsKB_OQ#Np)muW8!)cQf9hz}Jv_|8Nowo*fv9WhA71oV8=LDHTW^mTK8g#nXAR6h&gIq5i2Rk%DLk@M1 z);_}N<)Uz1iOG25#=YWPc~6mLgaylp0^r}Ay~~CFrKYqYBGl)jiU{zz4A`{Z*^*f| zdTQ!LN_IeoBRq0XblQE{s6*h}<0__dbQisSUSBTNaFks>{4oIl_K)PrukU|wb&s`7N3=}8kpL4 ziERpG`nPH_Nd&58_A5PZ*ofJgI0H}5f350HI!A0jKU+3w6db=dfa`#*>iP_NoigwR$z1_%NS}{Q5*Cmme&lY z2byv}h)KbJ2uu>SN}D82fF4Jn7IaYbn$X@n;lD6H=P7reN+U%)QRtHY(I9uJtfCdm z!Jv1#+9pJu=0o&`VjGVCpz^KOdbTAg?2_jHN7OZV1r}}nn_Uwp+fBBcYO-zHw(ZF_ zC%Y!wwmI20zTW!YTJJwN>)dnq+2^M%;n@Mg@N;^{o&h zSrQwyy(1Hzz)q!AL~$ZnKjfzF(+lyW1Dn;8Q4(#KP!oYf3FF-VwN~}43ejtz>#xkl zf323@IXYW6O%$-D#(kzYUowA~*D7BY3Zn>4eRX2SM#$>3u6>Wo2}a6KenyT@B<%rZ z*jTIJ!Uu-QhxND3l%0d!_wFE1qO^m8YVD)TnqT&`2Q?9yu4fkwH5;8FaAermsHK|! zDe-qu(v%k7``qYz8xxwdxlg!AG2Kv0SrWH5>yckdNZ2yF?yc*v@f6v9p% zrn__b1L4qc(m_;pRUM(8haN15Z{4W}rIy?8HQX-C{ABIA)v(6&+n=l%x#Q0e*q&0d zo`0pe0Q~l>Z4?=Sj0QRG_Kl1_F~3^sJy|shc*6Iqe^CE^r3am7-q(B8Hy6Dh%~54f zWUAkc8ygBUQB&2p8KSA?ACm~K*MnD-{(8V$c-wwD^-LxqCby+v?n}lWudr3Q99)Z{ zw)Aw>$Opu>r2cIPycg6=KN~^J-!rry5{}eM0HUM=zdmp&UbDV z;H_aTMKK^P^6|)kguPRS@GMcHE(!-d5`g+B&pO7MgV*42IJYZpn$Fl{FpM=qR>U9$ zNSV4mZckr)19NQ%{(^=Xyu75HK-}I;@^5`p95^0~#G=EYU{N5oq=T+2h_I0(9k|IvbwQG^ZEi- zuaZl66_Ik|by%np5CQzBEPJCj)+e!#jM&Qsb*LPA)mz)FNe?lrdX)t}Eo$%Y9yWW; zsOm%twH`fHA2>qV8oTAr$Rs@$5jk+ItwBoP*tjm0PD62!4^$2gjC@D=2ve7T&W0t& zdkVtREfA-KEp?`uF_(?WBSRtKweBvo6h9R#MW-_X&3Q9s%i)aqI1CmSf41NbGBy5+ zPPcZDtbci%KTglLF=7NGA(Ruo!&RgBM}k*stzJ?>01YCCMqG2m90d1RL?2P)9V3TLJYj}-41(rVD*_J&eoh?-vn@M_n7H;{uT z7_=X=w%uDV>^q$Qt_s8_?Y-NBnG@b&OSxp%)vzDaLkC!Rp>XgswfER?LzAD3*t4U_ z$=s(^G-_+dfh!{4&6GawuEX{9>!FUjYPM|MakzJKdfR_x!v^0Dh@lTTf|rs`!EMY_ zf3GY5ws7uvwfBnLyBo{UeK%e3@P-;H=j4^arJm` z=uG=dSf80NBnsn%b_Cn?n&`}$%23t_1u)P9x}gyln>6dHrs5#mU-G=MtZTL=oQ4)@ zC9LYLI%H)H5bNAT$uum$XZ6gdQpI((NSV&6#g&XZ1nFa|Wp6P-51MGdpO@5r^ORI$ zu+qgRntRkjipA3 zkyrK9@U~dYs_oX?A3PzHmP1ZKXDH|3qHh`GDXyD61ofJIra)_^VXU?aqFg4B3 zMi#gtDa)e%a6X1^bxMIVpI!ZBfgiW_tkPlgXI)gO zcfJ`gw^t;@_&bn0>mJJmp%M7FZ2hRj)7LC^(g4b0*Qhm_qW5vz>P{|bj~$xoT9E7i z9R>LM$k%L6fikEk_}Q3#A|Mu41w8sHXaegWC(m6?zCP*ip3aXSJ@-1B^nmYFlu z=Ox4Y=;PUI^n{{x-@!%m8lLI(kTlX~LNW=!gS0P&HU+Pr(YX*{6CKKk{RMjF5~P|TqRrzJToh)$a($srGly9z&(wS;0pL#e@%9XV)5ZPo>q{4u z;^aX&*_lpEscrJ{(B?mlm%*<-LgKcc&X8K)isxnG#jlxZnrAq*!i?IlvK%jiYb<5h zB$dH;0KU;&#;Nj&A?0s78&m2x3VkzbhM1mwm|a`YCfEDyro2Z!$&Vmr+lJy%?v&Ga zzw|pG7bLrVL4P z%WC~2h_QKZ4;hEAs|j;`0tou8NXOZ3gQ~qzDGAon8t%Lq3`-{ohSY&(d|+3?TAV)6OiHu=LW@tvuKPn3aTxB90)?$?ob|(hx@AV$-ey?+${!g6z(xbI z+q91o+o2LEl#?+3rySxsTj`6~;9p+w;$t9`1?#eU_jQyQ<~z3~ILmy3jxmz=EWS!rF96rs0@IV?hs zW4Tn-%;yMOuHu`4V?2ey(y1YtLW+K6OB5>$57_Yn} z#GIb}Gnp=t;zd->j6y9D_!XiT3+ngNdl*&R6s$8NYXv#^XDC!#RYWrnGgYv)G^V8j zdNBa-IGtl_#=8u$c~Ki@Ojs&Hc?`ZiRNsjIF^Y=J5Uo}~4O=`;R7BJ^0Uyx_YcHP0 zT1cN@DE*u5A0A`r6fO^K{@6XEqBNGA3k^%}uN$*?X?;Z5O#@>zhE|4Sh{l+tBQf-m zPjz$GL%h(c)({EdS{=aEyY}!x(#nPX6@dT`)yWmThpZx?>8AOIimprjprK(fg7;Vq z6mWZgXgja|ySB-UWb-odPUDoJ>$|&FCHo!a!D!%a7;J10zuI(yqkuF^&iWnG+?Q*m zs1^=Q9YKC^;tuCa`LF3%FzZ6WjR7LzG&|r~*-Qj6qCXRlBlPyS!rYI^^KC;rt3$7I zMB2FkPm#9{T*u`_t~+`71`_a-5TJa~^$S^*@B{vv#!&D-1Of63L9l=|jwi}YjZIET z%g!#0P0375&QFZbPRvM4PbDQGBO#@vOis$imppe9Pe-)o%l+E)eCthvZvu_z&J=i( zP=M}7#w(x2<5X6g?#IXD<);@PhrLPPr8D5j!Oxl43K-{IfE`CtF;JcNp6C4W?A3tnCjqq|o zAJd9D$@id=Bjx_a3Un|)4nuDcf6^tj9TdQi>Np}%|88cF1G7-+R_->ij}_>rEu>o0Eh0B!`T5amDT_ao|cxiVB}Y z>h>|LMW(r>)510UyYoX-yrgI+q_?Zd895}1)6N0gjrpAF#a|ydU^Z;OzO+N+q8^YF zkH=BwTznI=n_Jrg4?=AIN?B|Hp#(nKHjc@p{B7V)l-uW;@t|_0y{Dux(EytdbzYja zOQZ}%71NTGnYyA-U7Y8NI`Kbto!I5>odt1733mdaLu|U;)kkEbR7eckAMtF|l*Aq+ z+j<6%YuVlz!+EoDKYrM!0xf zlHmFVKsUlS7C@(>BUSPY&8+6fe5D)n8bb)WUzrX|sT26ul8`nN-<0Yfi!04-XML`P z3=TeVByXe+& z9Ohos1xv`uqyH#HPfrsHWpa!EY@`noyZY#hv&KJd$Jz>;0m;$q3;jHfCHsSp1mwaV z=}`ci8DH(D`FFD#sd?pVOyZD!aKoY*$s$9b+klz!vU_3Pv;=G{>vQ~P*CwsQ$!BnK zhn}JT#!5U=Cq>aui}As~ZjdM`M?RL-arXj7E(X{e&4#P0T?x_=GZIE(gV<4Gzmy8M zI#$?Y+u^x3*67`8z=PBUd9x5a7wKwJWDvCNAp)bEYo>G09Jrh3dqjb z7${1Vgin|HqiN5{cCXuEp_!B_!vzWKAqAp5EM3^1JRjPAd|oy_6m@!pt{e*AI-*8K zMr^JT?^@s*V_M~iOW!nHE6nILNKVq9vESPFQb;2>6tfL>u2gARtCGGT&Osw|n>!A4 zh!oWO*9A8Y_u9{mn)`^s5hK$6-hrnV#Ntt1K?of>QhmPV%=Q2U{6YadE`U!nYn#`A z%J{fO=wDW4>VycX*_=aheKM$kgm#_Rb<)KJ@14ulO9utJX?0-`=Zhn&iLp*H<7>Ii zg;=LYTxgl9x%_t#oXe<-e%5tTKe}{rn5Cv+j~`cKr+8c*WglS;cUazHV;o9$)O%P$ z26eHP66-5qz2s8fMvRUyu03uVmK?q_zOF;fzppHC0Dytg?m$FI|v2f(^&7~ zJHovna?eLmK&Fh)1J+5VBnj}qr?2gM^BNx?KhxU9+j7TeG7CXf2X|~|_Hsl>Q6n17 zjq;>5gmIg3JPL2U2n#fIf?iTg{96SP=0c-;;7flm?|qtTA|AQPI0Yh#>4DRUF*jaP zG)me{hCEY*&B3o-nJT2CKA_`_D4XaD7EY5r=Ldcy0pLvs9&{85E{QSscyfkdost_7 z)<$2ILhjUv2dsmA8^lnfGL1nISdOko+}fJo)UNrwt7@V+EN(w9UN^05I-{$+wBis9 zddk{)D1bm5X^&Tm`?D7{*F522iuP36b6&g*$~A9)MXY;Qg9>8`L2&~rAxs9q& z^gb%kS5EXI|qA`Zii> z-BnV@GEf-&0V0f$ETt5+*cy+f?Pe=4Aln&m&ORPLmPYh8v2G0sWCG-!k-Xz1ebI8T zCtvK9iXk3HZZ+4IR3QlfESC_xaId{QUmyDhHm%oB^2d^=TplWqd)lw2L`Q3&>Tf-$ z6x(8$HM~u#H{)qs498nB9-91gB`Ykp#nZY-gI*Y7myJ8NRcMef`Hrbd_6MXCS%Nmg zFMQZwT5w~$eIMk!Q4bL%4@Ae*`eea?D)4Q)B1B*3D_5-zaPqOt+9!FCryP>{XFSQ+ z=KwlLeJ=e&5pdf-mu>SsH|^~A^Ol{!{Ohbo-E8g?_RtcZ;t#k@xGE!LaEeyDk2+r*}u*hDgSLlY|#AfB4loDJq`L~qHy;TVp+vfN6LmByHdya zWMqYS{+!lkJOEZY_K!#exR4YOT4Lqn#yjRdHZfzQokyduFU0u^t|w(|EkZ*H7~yg` z9UZPquTZ2!QGBj!e)@2Io;4XuO5gkct%wR7A1zAAbCCcabdw`tF5d^4>kjwf@Q)T? zSP-Jz)IT~px1&Ey$2*xXU5>!vBxiNu)l^X`9Rb~0?WKO6B}{4)r}>>3V|9>;-J>J2 z@>!atxNuVi>jVG?tbhXdT7DutvvX_UCnh|pj;HoL$uW_cyvf%s9t^7wCD0h|fI15J z4H+No+pJp+IZZwsT_l~owuusqXYnsc|0~uI)bZV_S-ikqgrPQzToC=8gbZ3$&{u*= z8GJWQvy!sGe$W?e%!t6PAadTJ=Umn#I!t>7M^A0+mDjtOfa@XB=}x5_RIES(#_H{) zM(j4a-^%aeVj<87(yb>7wHXT4nXz%AdIOd>oZgCVTW=c#7NuN4F)I&r-~N*w-|u-1 zA8V0Zf-G`>@pEo*x{u_jh{VxRJU!mnl;$~stIG)MsW|x7!VOV&)rN8*BDRAez(G(& zA%xRt>Ey{R?OW|OXTH#)q03)#^K@HoAw>#yS#!E(B@WxI_3vbwvG1JVQQ zh9mB&-p384J@g=*T-sN(59E}17K5h-9QJ@=x|ywD+Lxb8pIc}0bF0zCrOP&J+~@k` zG0LLditBmlj6y^qhNQt=o53VrF%CLbhgk&e{$O!d?X9rE3+?Pz{v#z;$z_ql7NmI;{Gnz6T6_WSG!>yhU4 z(wX_?N(sZ$(W$1vg(f2|Xik=jf;Rp=L&|Sw-$Q<4&iy+!WBg?kf|U6;*IRQSeMH@+ zpBI|uF={gwW{)+EG%Jahox%l+6>+jY(!HyVP6f<7C?EV%cbXdT3ltG+wbuCSkv>{jxUWBB|1Gtt^^P_{ z*%Ha-3-j|DZTHFNOLs{8RrF`w~wa0xC6IV4DW za%GDaB1eD3?&*|4!b?Q#zFi3tdz6ME%IM4+HDhkb76h_1h`7j5XK8R`N>oEkZlL&BP=HA};2b zF}A6~!Judz*%ZE&pX-Z)%kR`v(W*$5-Ya?W7($raAG({hx8-oc6P6W(lboS#Cyb2m zyj0W{PT%&gS1k@Dka}Lz+5hgai_F7eE&`SC6oVpd}I{Gql8e2 z=w6jj(etncf~PZSddcTnvG_plTu8JBdgkk3c4bQF9C$f$!zfeZJx0*HZZzU{4oh$& zM#H+!#ioy(HzDZwG~Hw3Piz@ebg@2ULH@1l|8?NVRtMh^8|`5cIwS)9A-8Rem2GbbR1EY8MPFE5Tn=wMzkJE5X4c+cPWW+8J)l)l%bRwzeUVR16lSu(6vUDtZ7`=bJd$^Sz2 zT^HZ<=Z+Gr1dwuR**vtx%cbTMp&u57`>^q#@PNYje;F+8rk|r?AqQsw?@k+$h%D-E zW5DCLE+v}&rD{+rSRgazG$Aj%;0;(P$uaMSi9BQyt>C!08`c@hIb(l{-q&_gkr0(F z2WyB~v$E|c`nZQLll&ATBqzGM5pnc4F!|vl4IZ|tR_r#KM2I?S zf>limyHwf0Ehc(Q2h@5Vb9Rvj4%Q+#u5s;w-Kd>kMt9@kXLh_=T2YuO%2n^u5@_4w zP@*U`hG)xxel<4)1kIYhZ`_~M2}2o{$sVkn))x~etI&h$TDIR3$*EPi-fG^zA8{;} zD;HS6tH$D*g$=J4^+pv~iJ^_TW5sw1o6)3WWIXK!rAfq#f(PK~nx%jKxDX_{Q-qZL zEAU1(*HfxNGfw_@eF`IkE2OIW4kIfmvL_*p zlmS(MN$SKkx`uoa9DWBiq}y1V=1Tn$;fGL6nwV zP?1-VkW-XgR*)Z?6#F}_kd&01gqW0?IxRCLEj?q?vZKv(_UBQooMQV1x{(H`Qjth# z8Wo_{nSDF;a&+LYRd${Sc8jE4qyH+q7CD$F)>m;1&RUG&|BoLo{>6o2U1L>x_3ozpeoZ&r&B? zz|L-x%62Tbg!$irCz9>!;}%Ul9lze@Mta8vDi<<8M57);`B_YwnW+f9=tKX%cnR1g zN}UM({-vaxEv_Q4+9r6-o%c3zU57nnjerb$Cq#WL)c|rW1XD z#36w$U+2$PQ+|%4u8z-h`YxBI$I;wPVWj*P!OdrqRD4o^j|J)gK6mS&LYm5M|e%wF;xn(I`Pta@99 zQ)QexwZtz!P!OrST-PnL=7=YFz_!s`dhEy?CcVJ)>e$zT&6D+g+G7ke<~$RgKFU-W zr!PbKvKqi)cX-r9<5_)fT_;`pet*V;KNhxzs(8Q=?W~I`K?YrNmAM@y@3`&bztMcj zGZtz_qqRXRBe3?fvn2P@0|!25yfe}t8r}v+`&^~4E$PE@qk2S}=VD{JfP}7$kPb$q zD5YJEXh7$ImC|zf`Dx9xnmGz_YL#Gs3$i<|68&fwERr~4Y7`BTLZQ@nI3d+1+NK)x z1AC*+dYo1cV;)VGC3)z(fTlTV@gf7Thlup@sMqfF$GH7{S?Xk?gpb!NZr$qmBR(%q zGhy_d%Vk?8X#o6a9YW?TU|=V@#(&$Ud_G9#EtSP7M776!&U5T|A4#=B&Jf*9nSS~N z9yyZL;%JX5CVfq92r7K7b6@5iBHM1XQ+vw727dwOoqJ)UGw60~>mXjZ>G zqvs$C$!?z9WI0Qp?5@(rX<6z@|B}7yjI&q1FE~kL3ab=ajqN!!*rXtj^qIw}!_o0Knhf+_<~z@$ZzKu;5agcO^rbK4=aofJ z%rj~leM1qoT3&NU@;1|9jxUU$NHLoW8 zmOF?aTP$;fpA!}>f%Mwe@-qfyKmOI-fESEaq;!^P&WFCb@^3gF00Lw83fuO-P z=CGSw9W9!Jfnj9ma!>;lchZClPY6&R=SNyYQXm3vVFXkAO>B&@IEU~Z+#H>sEO>0x zDOnNG=JF@ch`l~NO(0mFjqK>>#q-;6-kN9>tcnu6!toqz%eXi7|LRQV?l z96s$`o)r)O>sk=ey@dJN!*I{Kj9gL{otY;qR5@j$4RsVCmW}>(^>y5ojViEHra9q} zv}&1vnf$RBk-Sl9|ICJN!gh5+$RoGwYz)S688k#e^O~;S3$s`^9-+Q?kF)G|kw80d zI`1=VuDwPmPrVAj<}gXW*(3k==6l1wwm$+5Hu{>8`C{qRQCvqb@(ituxcOK8lLxqP zV2|3jka@s#M%V9YkCDv;|Daz&YBBp(1L8>xSO7T!#Pmno>t{*MWv3$lZFh%eJ&N_) z^WRgN^32m?J?+dRhIs!`P*A(}sT{$laRMF{4j=P_b&E%tmJP66KT>O!D?*B?qYqYh z1jAbejz7%5JN`lcU|K9X55tmvWvDYr;5%ut58l=pRGSq#c=1pwXWuV7%R+s>^ap8b zoyBh2%@@L9A-~EDhd`lO^B@L=Fw;3%-=TdlrbKMqUAZ#EKuQflO%_XofUtXge5c>k zk*qhSqyKQfUZrDh?}EWud^hHauRI|tyXVBrpj%9~GmV>PvZxf7+-tNdUEy&tS6ydD zktXZ_In~*eK^riLy%HtG z1QhDz+}dn1S?a~`@NYl7idyN6)Z@K8?>}#XGVL!WSXV7WYH*vgK@DC-DBNnVjfIA< ztysz`y5T=!D8ik%{$%SWe+?znW)9veE2hMcs%8VD%aJjea(|4BFzdXq2{KG;xym9; z@DO)xspRgv^UZM>EsS6R-`=Sgb*%K;MSQ*PoEBjKtx4-F_l{&=ehd^eYtJyQumxp+ zX^z3n{X+9Ub!F@Ki>~Jn?DYvMq6RIW}voFZv1tT zzt}AjO2+)Sj|he@Z5mU|zC3>Uv0=3zkb$%GD_3_);vE+|muGbqW+v$E;Z9*f4$>rE zFxW`Y$`O{zsC7V^)%_0a9$N}9y8`gN-WdoO>4PAda!0(IAuvD^SZZsHztSl?>(5(% zLlI0OWHw1nb!Bb5Q!u$1oQ?84q3yOWCo4X8q|E{yrdd%$H^!zb=XIufL0iju&#*y9x* zJ}^5C6CnXEfffiewa5Py;Cc$9Po&_l&Q+lyMx8Z8coZIRJ31&j7j@LTIX(VZBi-s* z>MvA&+fmOucc#y)n6yxSyq;4c+gxGL;e?TyIsLov5EiMzk|nU(HmB*=!n!ezeBfvU zUhmRzSEDH0SCvaIVJMg~sbclqRA%jG-y*ER>5h|6voM(v?;*u&NsR#bIlLhf?qoIq zfRAU#h%Lh-QtV-c-;3RvdG`kfb`{jKcn-G$rLiO~ z_zBzn3VfWoqbN_6MHA|~1;OKbHyGnO|2*3K!qv_BT{H`Rvcmyj zh!bOCR1DPQ^V52AMCZJI4{lsoNm%K+Rk&Zg@9Qh#F;+iZ&vyXZb#N;&y$Te{3MwxM z;^2bG@Fy`og`dK$g|VPm$dc=c2zV%w9e)DIPQC@o+Gt&<&#aOMq4f`Z0ygp~DrQ9O zx~Bu@=f6%-8`vFL(wXIq_U6jVB{%5-KN_H!m`Z~3qy_*AJzg`27b$F#Z^-R2{7?^k zbnqy3dlR?bgAT~G?7|X?8yx{7jR`oK%lfX6olHc4V!L;8M7YptT?F(pd-cpiQaU$M zG8M4WINhwDuf7%7oHFv$>9&1vm+D7Bd^#Yg5N70fkTen3^^1fBoLs(Mp;h(1c70al z5J%Abw{+CVr8Ix6;w)ny3GvrS_GCZ{V@h0YPMUp4iS?}?WdWp`r5AD9ha~qTwPtgs@{e!=%qwJKBGv2t6TwWq z0gN;+UO;rJwuvvRFZ}`oj#oPJ7uzRKBjRln=qPoA&ll$S`138a9}I722@qy9!>FObPqy4zq(fGQuxob#j|9u1z1k6Ny!fWw?+WUhBi-7hp zvuL1qeHUl6aA~X&8k|_YLX)0tThl>}7wB)C|UtVNd%e-Q3fYzw@K1;Vppv;93LXliHt(|N+*qCAJ{>1Z%dJ9aBdY*37BtrzM zay8);C!cPGm|xsj+W%7{BE8dOYaXFOKBwKrT-Lmr7Ntj@M|-@7R^J;ph^G?yfCMkJgDYhVC10wI#`4!KiEV=2q9IWR$NjPXF=5 zj6s5!XKwpNG3v>ZKS1Bg*br52hDA4A7ElS?*QXcZ3?{+h)wdxBwDAQc<74s>v{!`D zwZ=3xU058jjJ9YY|Kr#DYmc+uzO=%8KH&AJ&@bx9jb7;r5bD&BdPzkcNrzSGbKQqJ}vpX;+ zt>JYLCE2M`=VRzFh2f~vFoK9js_K%;AeF^w zL91}t%HB~4vqC59GbahA1y8eIU2mzACDE*fc@2A}Tx>*jUMiM|NcjQrWTPU}azWEG z|21fI+x`!q_|GT|Yh7%0tjx5m)QqCMoUGEq?3%LTg5vVZuavFQiW+>+j&OFnPrX;9 zFQ%xH2N~yrT(5$9g8>4t>dbk2oqJpLaQ?SbaEPVLgS|HS!RWxOzbkTLKdzw;tD`yI znnB9MZk%hssSbaFxv&o1G?jRiRB4;lX*J*7i)b@eJmEHOw=wM3p?a|iu~=z;(*NfWjH%8F9*LMa zXijY(K}A_}X4T~aU*?EY^(CmaqUYO}%fiZ$VE?5N$(6P9W!<_J7-JtH2ex=ziwT=k z?tdM$U6|XCUQNc@CdVYrw+qfQQA>_oY{C)YJQYX~1&FbJFtnYp8=W+k@k6J`T=W{6D%MG4|MG z(}vKOKdBz}=t3EP;p;aLu=}RhQG|W$l{-%39cox(MvwavzCfkA2Gj=_f!Idvw)SiB zhjJ#_E3|P=)Ulkp;GVImVBNEItNn{FRJG__Mjzr~Z zbUXlcw6_5dzS*WzvRl3M`|fK08h<6Qo}~)X50rmfEgEuDq9aVx>TJ@ia$U$zsFE}Pod>Los^iwVvgvo|*zD;m(%i8GK2#E!&M#~MgF(?1Q> zsvS9Ji=&yhRgQPaN~Vn%tKm`t-i1Wq_-@C~bL3^lp#*$*ph6Ep3~q8)brNo8ckit{ ze*sW!l$D4`fa?mq+(nl;#5YJlTBfERmGhN;PhTFu?}Y8B`CUWEl1J;zpyQ%>Xh!8{ zp-5X&kp{|f^}?b4k^qHz9r#{M0r}nMqB3U)*5%fOPn$*YBa`{Q1ef$h_{{<;=Jm8U zoe-bNR-{E53n|l5lA@Y~y0xb5z?sKkdX2;HftEt;f>ilecpeR7q9Md|JT;T36wwn6 zdT`38yqH~)dg#^I{?BJh5Wq{+80VfeS8GA<2hD-DlJerc%QT!?EbzhA(IzXU(e)FI z*rL7Hp$S(YBl%2p5e;A>s7dqqIKKLQYpTg)<86QLioKSS))6vwkPePzg_)1Fk^MV` z%VSaoWq9T;tnm*b*1MGsG~=&B8uys~X=JS)B%;G3a}kM4X&iUMxlNZgUOMt4nBu5& z61DH3#ocnu7{Zi_(v>*^kV{Yz0QUs>5=ArCdSBY1;A$>8CCl#Qmw~Ms8w~iM?ufLF zJ%xH}re<$(W_!NpmOA#% zos*M=$+O`-;&~3BUEq_Ev((G%z#v6?dAHt<3r>4h%e?BxRkiRN5KnTA&XCw0tWbe@ zc!lY~>xQNKFnl^#FLRH77AT5y9N~Lu$wmhmpN;d6?0}U>t4lIA?&5WTRWk?oh4L9~ zpK(1y{CU&#WyvtE3PFd8QmzZPN^%0KvM(2R&YphLZ|P>fYwzefD)2|6we^TQpr+{# zxa?Pxjl4rU)d!%_&iHd~2wKHq4N_v=mxvMj_xbqigH(kUR?Fd^u1aXzE}kF3H{==J z>1s{ecwKo4Pgg-;^~q#GZEtE=-$Wi*(e@r3S~r<6)d=R$qeqWA)>;O<2sbh5C}z8+ zDIYtvPHf}&;{lu4-ov(HuW;$Ua?G2Z2p@C6;7z4ctv=W*?1`C3ZR2J1?`86?cvhm^X1;5iA+t_H%@%Eb{UDy3rdLX~ncp}U23Kiiir^wUSI}DbZSR$1l1mUMe{COTqKb}os z4W(YJ_~_7S9gkZz#=+xhAXyne7UU^G_HeiZc}vQoGO8FpFDTFuBO>@E|43IM#s4!> z8+jturP+E=WJS?Rs_>i$bljsvCa}6i zBiWd$s=<~m6Wv=E44<7$)yHz-iODgJ(V zlr*nBI29e<=|U7&v$xfO$yV2K$Z}9j@>(i60#q0hdmq1Uu1KqRqiRX~A&-2kdAQ}h zw)4~t_qI6fWL?~$5R0&~G2}dpEfDVL<%VEBrGhDr9I_=;NpQ(U=sPlvVDlGqw5$(G zgJ8sPjbuLZUs0jb4eO(qpMmYQ5;OXPj<)6kSIqFt^7GOLXiWgJxxCW|gdaBbo&=?@ zZBUzp&B-y#^R*4glCHD^{D1UzMN0jf0hagHpW35)=fcas}m+XOSLqVW0Wu= zr;Aj`bZBax&JwyQkXGAW6(VOyQT#ntIN^hv5GEJ#{yM8Wf}!tYc{`x6i+p$Ow{H?7 z4>4Tj&O!vDG&FD<#iCHV5@ImtcNwYheSd=E3`@|TE2PuQ12t>M7TgE0+e5A#8}orM z(9M&#c>0=8*}!H(i@pR_*>O=rD@k_+D|iSy!u3_ps|$0*(JVOUYzK~iFHMKGP;E02 zuHnuI?4+xfr)H7S$QIrC=_8Dt{aV==i@Cne>Qf?;bE7GzjG|@`A z;SLdjOx~C2pM^CGk|&(d6jx_LWf|a%23cvf*a_E`My#aWLK*z8CQbUkLcxE3Ll)e6 zkNWYw$wUok2lWI0fa$0qh@Aop;McSW!HKnqD5gkFtai>u3j*_t!)`Jq{XK<$06%u} zta`ofs7mh~Z+MhYk&a8E^caAc@e4TkQIz4&5ectya@Sgtn$8W~AA@orw&2gdO=?xS zLSpwrQ$izG_25QfSfW<$X}>eJQ;krXxR!k%C9XGNp|EQ$F=%L&NeqE0_~9<8h^wFM zk5^&i;!8Z_P()u2c_MY!4JV+p>UD}Vyoq!bv})(B&i-6$^xe294fL<_l2r@>JC^C^ zIA{6oVZlWK@!4MGVMA2J)^X4LFZMAe!zrFtVYMPve=`^L5DgSQIa3`nTgD_*lMkj z#@`Mm@O>N2N+wW1L)kkN#$&1Indp4(_3(7Fp>$fkPT31T!P@gwcvjFtz?7af=!jUr zhH?HfGw@B0Yqe*ZCycWX-3ad*y=8G@6%1fMEJ^#DRLdPg1qq1+K9YjqUWy`Y5#J9! zE7mUIt@|z&;=8Y~DDqGs)3_3o;dx+iqYmg3nbSyY6h9d6XtVw0ItytHxY9p$S@1{0 zSpjF~J%#;MD`S08ypC|K0grcNeV|`#*oJPX(UTPhMjMR&u?SsLJHtQ-e1vj*&T!wJpb(;WrEt|tIJu91Qm7{wX9O%;A&x#-aPABeT#H35;WW&03FmCPB z++oQJ<^5^CJ)6u9!fXzOK-9Q@bV2sL5M;U2LPNJn_Ec7L(I~a0c89QY<(hI^NRYcBcA{y{c08-+BJa}KJ@izQM+#0d-(ao zLUxtHF_Dw*Z(suMU%cD(+tK3WxM-?UUpj!hEdNcvp=bh~^_<$n`@4IqRlqt_zye4l zY-G{C<9#u2xaQ-%4d^#0$~N((xz5G_tP?f0ynA7qQ?g3=#(!`B;**!jhSr@wO1%b< zYNp_pzbxj~i10f|a>}*DnghT=eHWB2K^cKg$H%|t#XzKCq}zwV1Yx{H%|1-a6%`&e zb+S#)XsdX$^uQjO{7K?=W7d3(@&0)0wxstvrf1R@Y)ksmGNxMB=YE@Mk3Sr3{UHg` zdgGXlHcnw?a;oWS1jRyU-&2h8NzG5;#{BOr4T~|*gz!1A_J$vs<*4P|4MK zxC%M1oDM<3fsdBAE8Ch!xDc4eo$%O~voBisYb>x8NMSMW=f}mVE7r5?C6Z28djzfZbAuL&@TF89!Wx3g)!39RPMsOfNPfi|h?$8Z z$L>NS^$+T)nreLs!RKTL-`n7=!kiGPEJ?x=`z$;j zdnB7Aj0_D5V^`G2@#eC@jZPbIC`67MrllPo;$D$Msos$D6dNlj}mHeX; zYX%KF_WvCl;G!}AgA~5xZ9vBxsmINs)2&C|UNXcfnCCm5vEMLpQC*tv-M-u!}@eARhYOzco!nZiW z;Nl5?&24A1@5uJW5m%23kC{?txi{LnlYZ~duc7133}zl!-(9e-Q_(iV-r^|=U!i(} zH0+;CQb*O4v(ZK*KvHkzE=F#jA3W>3UN8NOKPKqkWo5jg3i{nhnLA9H_jU7#j{+XFl$SWQR&lwwj4w4-HyD4`aV%fO-TIf;bpN2YFCdA}Vt z6;WbSYdLL@trLkpYGjP9I0Y#@R7#~aKr(AQ_f~tn6!{JfbcT7I((FwkQOe=8*sD^O zf>gC3Oln!77V8CIXE2VOxb zKJDj~9rk9V8T*J757`)x>XM0Q7EfKU0t+F4FVkV+XiTkrp<G z#o8sa7d2nKqAgBYQqAneFD5i`@vF|DG!j?vX7nKr>-~T$;QshNI|{DPdA%(&>X(hKaO>%?mF!&341dRGQ!;z-t#q8l|ct>{4=#zLs70rs&U0 z)+n{FfE9;tqz=!`GcuIt$6YZJfO-{G(rnSfi2HdQjh(7 zo5{_8AAQk|A2V;I2#a;MiY?dY^_iO_xX*~DM3B4os(i5w)I~h;LKo1&$<@-U#KiW=bY$!G*{LR|h+O*E}T8 zc@;i$Fj4-SiG`|z=3>l%)u~dn-Xdr(s}FE81eeu7L%YCx2>?Eb{GcarJ}@*Bwc%YN z>3}}JDmqdL2C%-8$p6+q95|f)P=m+uUT)^JaY|&@Y08`|(Gq(1(B>Xwc-{!`@nTD} zAQsOp*6bxNX|&+wA{46H*GqYLqJsYR&S>-qux7>do~Vkxi;GDL`|67`zg@X^|a5_RMrz0MvT+R%nZF_!EW2^4xsr7M ztCgkazFx<*7^q7L^;fp!RsjHRr@dESRZ+17FS4e;pl9e?ELNaNpYiP7MFdOfWXk^x zl&O*u3;@NtB>Z(ve?3uTYHX43XW-dA@Opj*hABS)Os1x$_?d8P0D$5%N`Ym~9*%z= LruZfRxB+Sa{Qn(c literal 0 HcmV?d00001 diff --git a/Resources/Audio/Machines/license.txt b/Resources/Audio/Machines/license.txt index 5c7ec91fe2..71c2b9b9da 100644 --- a/Resources/Audio/Machines/license.txt +++ b/Resources/Audio/Machines/license.txt @@ -17,3 +17,7 @@ reclaimer_startup.ogg - https://freesound.org/people/cmorris035/sounds/319152/, airlock_open.ogg and airlock_close.ogg are from: https://github.com/tgstation/tgstation/tree/5f5002b21253354c20ea224be3f604d79299b37e/sound/machines machine_vend_hot_drink.ogg original from https://freesound.org/people/waxsocks/sounds/402603/ CC0 (by waxsocks) and edited + +scan_loop.ogg from https://freesound.org/people/steaq/sounds/509249/ CC-0 by steaq + +scan_finish.ogg from https://freesound.org/people/pan14/sounds/263133/ CC-0 by pan14 \ No newline at end of file diff --git a/Resources/Audio/Machines/scan_finish.ogg b/Resources/Audio/Machines/scan_finish.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7d1a737fc216b19ee363d38f9127c945035a2063 GIT binary patch literal 5149 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�P&%CJ%bGR;9!%csyJ;@-$`q1YXG)> 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 literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl index bb70d59d48..5380ac84d7 100644 --- a/Resources/Locale/en-US/machine-linking/receiver_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/receiver_ports.ftl @@ -45,3 +45,9 @@ signal-port-description-med-scanner-sender = Medical scanner signal sender signal-port-name-med-scanner-receiver = Medical scanner signal-port-description-med-scanner-receiver = Medical scanner signal receiver + +signal-port-name-artifact-analyzer-sender = Console +signal-port-description-artifact-analyzer-sender = Analysis console signal sender + +signal-port-name-artifact-analyzer-receiver = Pad +signal-port-description-artifact-analyzer-receiver = Artifact analyzer signal receiver diff --git a/Resources/Locale/en-US/station-events/events/bluespace-artifact.ftl b/Resources/Locale/en-US/station-events/events/bluespace-artifact.ftl new file mode 100644 index 0000000000..a7c948ef3d --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/bluespace-artifact.ftl @@ -0,0 +1,9 @@ +bluespace-artifact-event-announcement = Our readings have detected an incoming anomalous object. Please inform the research team of { $sighting }. + +bluespace-artifact-sighting-1 = bright flashes of light +bluespace-artifact-sighting-2 = strange sounds coming from maintenance tunnels +bluespace-artifact-sighting-3 = otherworldly structures +bluespace-artifact-sighting-4 = incomprehensible alien objects +bluespace-artifact-sighting-5 = unfamiliar objects in strange places +bluespace-artifact-sighting-6 = unknown alien artifacts +bluespace-artifact-sighting-7 = explosions of light accompanied by weird sounds \ No newline at end of file diff --git a/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl b/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl new file mode 100644 index 0000000000..11937ea17f --- /dev/null +++ b/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl @@ -0,0 +1,29 @@ +analysis-console-menu-title = analysis console +analysis-console-server-list-button = Server List +analysis-console-scan-button = Scan +analysis-console-scan-tooltip-info = Scan artifacts to learn information about their structure. +analysis-console-destroy-button = Destroy +analysis-console-destroy-button-info = Destroy artifacts to generate points based on how much has been unlocked. + +analysis-console-info-no-scanner = No analyzer connected! Please connect one using a multitool. +analysis-console-info-no-artifact = No artifact present! Place one on the pad then scan for information. +analysis-console-info-ready = Systems operational. Ready to scan. + +analysis-console-info-id = NODE_ID: {$id} +analysis-console-info-depth = DEPTH: {$depth} +analysis-console-info-triggered-true = ACTIVATED: TRUE +analysis-console-info-triggered-false = ACTIVATED: FALSE +analysis-console-info-effect = REACTION: {$effect} +analysis-console-info-trigger = STIMULUS: {$trigger} +analysis-console-info-edges = EDGES: {$edges} +analysis-console-info-completion = COMPLETION_PERCENTAGE: {$percentage}% + +analysis-console-info-scanner = Scanning... +analysis-console-progress-text = {$seconds -> + [one] T-{$seconds} second + *[other] T-{$seconds} seconds +} + +analyzer-artifact-component-upgrade-analysis = analysis duration + +analyzer-artifact-destroy-popup = The artifact disintegrated into energy! diff --git a/Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl b/Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl new file mode 100644 index 0000000000..a8e5d7bf27 --- /dev/null +++ b/Resources/Locale/en-US/xenoarchaeology/artifact-hints.ftl @@ -0,0 +1,27 @@ +# you shouldn't be creating new hints for every effect/trigger +# try and reuse them so that a hint isn't a dead giveaway. -emo + +artifact-effect-hint-mental = Cerebral influence +artifact-effect-hint-environment = Environmental disruption +artifact-effect-hint-electrical-interference = Electrical interference +artifact-effect-hint-displacement = Metaphysical displacement +artifact-effect-hint-creation = Matter creation +artifact-effect-hint-consumption = Energy consumption +artifact-effect-hint-release = Energy release +artifact-effect-hint-biochemical = Biochemical disruption +artifact-effect-hint-destruction = Station-wide destruction + +# the triggers should be more obvious than the effects +# gives people an idea of what to do: don't be too specific (i.e. no "welders") + +artifact-trigger-hint-electricity = Electricity +artifact-trigger-hint-heat = High temperatures +artifact-trigger-hint-physical = Physical trauma +artifact-trigger-hint-tool = Tool usage +artifact-trigger-hint-music = Sonic vibrations +artifact-trigger-hint-water = Hydro-reactive +artifact-trigger-hint-magnet = Magnetic waves +artifact-trigger-hint-death = Life essence +artifact-trigger-hint-radiation = Radiation +artifact-trigger-hint-pressure = Extreme pressure +artifact-trigger-hint-gas = Gas \ No newline at end of file diff --git a/Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl b/Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl new file mode 100644 index 0000000000..7e1ebc7a57 --- /dev/null +++ b/Resources/Locale/en-US/xenoarchaeology/misc-artifact.ftl @@ -0,0 +1,4 @@ +blink-artifact-popup = The artifact disappeared in an instant! +foam-artifact-popup = Strange foam pours out of the artifact! + +shuffle-artifact-popup = You feel yourself teleport instantly! \ No newline at end of file diff --git a/Resources/Maps/Salvage/medium-pirate.yml b/Resources/Maps/Salvage/medium-pirate.yml index bb856e16ea..e9abeed88a 100644 --- a/Resources/Maps/Salvage/medium-pirate.yml +++ b/Resources/Maps/Salvage/medium-pirate.yml @@ -749,7 +749,7 @@ entities: parent: 52 type: Transform - uid: 79 - type: AngryMobsSpawnArtifact + type: RandomArtifactSpawner components: - pos: -0.5,-0.5 parent: 52 diff --git a/Resources/Maps/kettle.yml b/Resources/Maps/kettle.yml index 33073ceadd..e4c0604c8a 100644 --- a/Resources/Maps/kettle.yml +++ b/Resources/Maps/kettle.yml @@ -207974,7 +207974,7 @@ entities: parent: 82 type: Transform - uid: 25103 - type: JunkSpawnArtifact + type: RandomArtifactSpawner components: - pos: -69.5,-47.5 parent: 82 diff --git a/Resources/Maps/marathon.yml b/Resources/Maps/marathon.yml index 4a729d11d4..71ecb20ad9 100644 --- a/Resources/Maps/marathon.yml +++ b/Resources/Maps/marathon.yml @@ -129076,7 +129076,7 @@ entities: parent: 30 type: Transform - uid: 15282 - type: BananaSpawnArtifact + type: RandomArtifactSpawner components: - pos: 36.5,12.5 parent: 30 diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_science.yml b/Resources/Prototypes/Catalog/Cargo/cargo_science.yml index c7c20e1510..7380ae1234 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_science.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_science.yml @@ -7,3 +7,13 @@ cost: 500 category: Science group: market + +- type: cargoProduct + id: RandomArtifact + icon: + sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi + state: ano13 + product: RandomArtifactSpawner + cost: 2000 + category: Science + group: market diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 6d3fd7468b..8a147be55d 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -157,6 +157,8 @@ - id: DoorRemoteResearch - id: RubberStampRd - id: ClothingHeadsetAltScience + - id: AnalysisComputerCircuitboard + - id: ArtifactAnalyzerMachineCircuitboard - type: entity id: LockerHeadOfSecurityFilled diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index ac447e4bfa..b6492dad4a 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -320,6 +320,8 @@ - SolarControlComputerCircuitboard - PowerComputerCircuitboard - GeneratorPlasmaMachineCircuitboard + - AnalysisComputerCircuitboard + - ArtifactAnalyzerMachineCircuitboard - Signaller - SignalTrigger - VoiceTrigger diff --git a/Resources/Prototypes/Entities/Effects/bluespace_flash.yml b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml new file mode 100644 index 0000000000..3421f0d9a6 --- /dev/null +++ b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml @@ -0,0 +1,13 @@ +- type: entity + id: EffectFlashBluespace + noSpawn: true + components: + - type: PointLight + radius: 10.5 + energy: 15 + color: "#18abf5" + - type: TimedDespawn + lifetime: 1 + - type: EmitSoundOnSpawn + sound: + path: /Audio/Effects/Lightning/lightningbolt.ogg \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml index f57703c744..38b5288eaf 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/artifacts.yml @@ -9,16 +9,12 @@ - texture: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi/ano01.png - type: RandomSpawner prototypes: - - BadfeelingArtifact - - GoodfeelingArtifact - - AngryMobsSpawnArtifact - - JunkSpawnArtifact - - BananaSpawnArtifact - - HeatArtifact - - ColdArtifact - - RadiateArtifact - - GasArtifact - - DiseaseArtifact + - SimpleXenoArtifact + - MediumXenoArtifact + - MediumXenoArtifact + - MediumXenoArtifact + - ComplexXenoArtifact + - ComplexXenoArtifact chance: 1 - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 0ef1403436..3dbb4be59a 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -131,6 +131,22 @@ materialRequirements: Cable: 5 +- type: entity + id: ArtifactAnalyzerMachineCircuitboard + parent: BaseMachineCircuitboard + name: artifact analyzer machine board + description: A machine printed circuit board for an artifact analyzer + components: + - type: Sprite + state: science + - type: MachineBoard + prototype: MachineArtifactAnalyzer + requirements: + ScanningModule: 3 + Capacitor: 1 + materialRequirements: + Glass: 5 + - type: entity id: ThermomachineFreezerMachineCircuitBoard parent: BaseMachineCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index 9aee9c1332..77fa621842 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -145,6 +145,17 @@ - type: ComputerBoard prototype: ComputerResearchAndDevelopment +- type: entity + parent: BaseComputerCircuitboard + id: AnalysisComputerCircuitboard + name: analysis computer board + description: A computer printed circuit board for an analysis console. + components: + - type: Sprite + state: cpu_science + - type: ComputerBoard + prototype: ComputerAnalysisConsole + - type: entity parent: BaseComputerCircuitboard id: CrewMonitoringComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml index 23785f4d6f..ddd4ebe997 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml @@ -44,6 +44,7 @@ - type: Weldable - type: SuppressArtifactContainer - type: PlaceableSurface + isPlaceable: false - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml index 4eec0ef013..3e50531a85 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifacts.yml @@ -10,6 +10,8 @@ sprite: Objects/Specific/Xenoarchaeology/xeno_artifacts.rsi netsync: false state: ano01 + noRot: true + - type: Damageable - type: Physics bodyType: Dynamic - type: Transform @@ -31,152 +33,34 @@ - type: Appearance visuals: - type: RandomArtifactVisualizer - - type: PowerConsumer - voltage: Medium - drawRate: 500 - - type: NodeContainer - nodes: - medium: - !type:CableDeviceNode - nodeGroupID: MVPower - # sadly, HVPower and Apc cables doesn't work right now + - type: StaticPrice - price: 2000 - - type: Electrified - requirePower: true - noWindowInTile: true - highVoltageNode: high - mediumVoltageNode: medium - lowVoltageNode: low - -# Telepathic -- type: entity - parent: BaseXenoArtifact - id: BadfeelingArtifact - suffix: Badfeeling - components: - - type: TelepathicArtifact - messages: - - badfeeling-artifact-1 - - badfeeling-artifact-2 - - badfeeling-artifact-3 - - badfeeling-artifact-4 - - badfeeling-artifact-5 - - badfeeling-artifact-6 - - badfeeling-artifact-7 - - badfeeling-artifact-8 - - badfeeling-artifact-9 - - badfeeling-artifact-10 - - badfeeling-artifact-11 - - badfeeling-artifact-12 - - badfeeling-artifact-13 - - badfeeling-artifact-14 - - badfeeling-artifact-15 - drastic: - - badfeeling-artifact-drastic-1 - - badfeeling-artifact-drastic-2 - - badfeeling-artifact-drastic-3 - - badfeeling-artifact-drastic-4 - - badfeeling-artifact-drastic-5 - - badfeeling-artifact-drastic-6 + price: 500 - type: entity parent: BaseXenoArtifact - id: GoodfeelingArtifact - suffix: Goodfeeling + id: SimpleXenoArtifact + suffix: Simple components: - - type: TelepathicArtifact - messages: - - goodfeeling-artifact-1 - - goodfeeling-artifact-2 - - goodfeeling-artifact-3 - - goodfeeling-artifact-4 - - goodfeeling-artifact-5 - - goodfeeling-artifact-6 - - goodfeeling-artifact-7 - - goodfeeling-artifact-8 - - goodfeeling-artifact-9 - - goodfeeling-artifact-10 - - goodfeeling-artifact-11 - - goodfeeling-artifact-12 - - goodfeeling-artifact-13 - - goodfeeling-artifact-14 - drastic: - - goodfeeling-artifact-drastic-1 - - goodfeeling-artifact-drastic-2 - - goodfeeling-artifact-drastic-3 - - goodfeeling-artifact-drastic-4 - - goodfeeling-artifact-drastic-5 - - goodfeeling-artifact-drastic-6 - -# Spawners -- type: entity - parent: BaseXenoArtifact - id: AngryMobsSpawnArtifact - suffix: Angry Mobs Spawn - components: - - type: SpawnArtifact - maxSpawns: 5 - possiblePrototypes: - - MobCarpHolo - - MobCarpMagic + - type: Artifact + nodesMin: 2 + nodesMax: 5 - type: entity parent: BaseXenoArtifact - id: JunkSpawnArtifact - suffix: Junk Spawn + id: MediumXenoArtifact + suffix: Medium components: - - type: SpawnArtifact - maxSpawns: 10 - possiblePrototypes: - - FoodPacketSyndiTrash - - FoodPacketSemkiTrash - - RandomInstruments - - ToySpawner + - type: Artifact + nodesMin: 5 + nodesMax: 9 - type: entity parent: BaseXenoArtifact - id: BananaSpawnArtifact - suffix: Banana Spawn + id: ComplexXenoArtifact + suffix: Complex components: - - type: SpawnArtifact - maxSpawns: 20 - possiblePrototypes: - - FoodBanana + - type: Artifact + nodesMin: 9 + nodesMax: 13 -- type: entity - parent: BaseXenoArtifact - id: HeatArtifact - suffix: Heat - components: - - type: TemperatureArtifact - targetTemp: 400 # around 125 celsius - -- type: entity - parent: BaseXenoArtifact - id: ColdArtifact - suffix: Cold - components: - - type: TemperatureArtifact - targetTemp: 150 # around -125 celsius - -- type: entity - parent: BaseXenoArtifact - id: RadiateArtifact - suffix: Radiation - components: - - type: RadiateArtifact - -- type: entity - parent: BaseXenoArtifact - id: GasArtifact - suffix: Gas - components: - - type: GasArtifact - -- type: entity - parent: BaseXenoArtifact - id: DiseaseArtifact - suffix: Disease - components: - - type: DiseaseArtifact diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 8e0733b3b8..e081701f8f 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -335,6 +335,46 @@ energy: 1.6 color: "#b53ca1" +- type: entity + parent: BaseComputer + id: ComputerAnalysisConsole + name: analysis console + description: A computer used to interface with the artifact analyzer. + components: + - type: Appearance + visuals: + - type: ComputerVisualizer + key: tech_key + screen: artifact + - type: ResearchClient + - type: AnalysisConsole + - type: DeviceList + - type: DeviceNetwork + deviceNetId: Wired + - type: SignalTransmitter + transmissionRange: 5 + outputs: + ArtifactAnalyzerSender: [] + - type: ActivatableUI + key: enum.ArtifactAnalzyerUiKey.Key + - type: ActivatableUIRequiresPower + - type: UserInterface + interfaces: + - key: enum.ArtifactAnalzyerUiKey.Key + type: AnalysisConsoleBoundUserInterface + - key: enum.ResearchClientUiKey.Key + type: ResearchClientBoundUserInterface + - type: ApcPowerReceiver + powerLoad: 1000 + priority: Low + - type: ExtensionCableReceiver + - type: Computer + board: AnalysisComputerCircuitboard + - type: PointLight + radius: 1.5 + energy: 1.6 + color: "#b53ca1" + - type: entity parent: BaseComputer id: ComputerId diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml new file mode 100644 index 0000000000..f65025c522 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -0,0 +1,68 @@ +- type: entity + id: MachineArtifactAnalyzer + parent: [ BaseMachinePowered, ConstructibleMachine ] + name: artifact analyzer + description: A platform capable of performing analysis on various types of artifacts. + components: + - type: Sprite + noRot: true + netsync: false + sprite: Structures/Machines/artifact_analyzer.rsi + drawdepth: FloorObjects + layers: + - state: icon + - state: unshaded + shader: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + - type: Physics + bodyType: Static + canCollide: true + - type: AmbientSound + enabled: false + sound: + path: /Audio/Machines/scan_loop.ogg + range: 5 + volume: -8 + - type: Fixtures + fixtures: + - 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 #really freaking high + needsPower: false #only turns on when scanning + - type: UpgradePowerDraw + powerDrawMultiplier: 0.80 + scaling: Exponential + - type: ArtifactAnalyzer + - type: DeviceNetwork + deviceNetId: Wired + - type: DeviceList + - type: SignalReceiver + inputs: + ArtifactAnalyzerReceiver: [] + - type: Machine + board: ArtifactAnalyzerMachineCircuitboard + - type: PointLight + radius: 1.5 + 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 4569a642f1..dd3b6b9a48 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -298,6 +298,8 @@ - EmitterCircuitboard - GasRecyclerMachineCircuitboard - SeedExtractorMachineCircuitboard + - AnalysisComputerCircuitboard + - ArtifactAnalyzerMachineCircuitboard - type: MaterialStorage whitelist: tags: diff --git a/Resources/Prototypes/Entities/Structures/Machines/research.yml b/Resources/Prototypes/Entities/Structures/Machines/research.yml index 851619e1f4..d35b71a2a6 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/research.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/research.yml @@ -52,7 +52,7 @@ map: ["enum.PowerDeviceVisualLayers.Powered"] - type: ResearchClient - type: ResearchPointSource - pointspersecond: 100 + pointspersecond: 25 active: true - type: PointLight radius: 1.5 diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 5405f2ecbe..4ea6e56068 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -1,4 +1,14 @@ - type: gameRule + id: BluespaceArtifact + config: + !type:StationEventRuleConfiguration + id: BluespaceArtifact + weight: 5 + maxOccurrences: 5 + startAfter: 30 + endAfter: 35 + +- type: gameRule id: BreakerFlip config: !type:StationEventRuleConfiguration diff --git a/Resources/Prototypes/MachineLinking/receiver_ports.yml b/Resources/Prototypes/MachineLinking/receiver_ports.yml index 397688b601..16cb34e9bd 100644 --- a/Resources/Prototypes/MachineLinking/receiver_ports.yml +++ b/Resources/Prototypes/MachineLinking/receiver_ports.yml @@ -62,3 +62,8 @@ id: MedicalScannerReceiver name: signal-port-name-med-scanner-receiver description: signal-port-description-med-scanner-receiver + +- type: receiverPort + id: ArtifactAnalyzerReceiver + name: signal-port-name-artifact-analyzer-receiver + description: signal-port-description-artifact-analyzer-receiver diff --git a/Resources/Prototypes/MachineLinking/transmitter_ports.yml b/Resources/Prototypes/MachineLinking/transmitter_ports.yml index 3ce176d59a..7282253347 100644 --- a/Resources/Prototypes/MachineLinking/transmitter_ports.yml +++ b/Resources/Prototypes/MachineLinking/transmitter_ports.yml @@ -49,3 +49,9 @@ id: MedicalScannerSender name: signal-port-name-med-scanner-sender description: signal-port-description-med-scanner-sender + +- type: transmitterPort + id: ArtifactAnalyzerSender + name: signal-port-name-artifact-analyzer-sender + description: signal-port-description-artifact-analyzer-sender + defaultLinks: [ ArtifactAnalyzerReceiver ] \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 308c368c63..f49ca51063 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -176,6 +176,15 @@ Glass: 900 Gold: 100 +- type: latheRecipe + id: ArtifactAnalyzerMachineCircuitboard + icon: Objects/Misc/module.rsi/science.png + result: ArtifactAnalyzerMachineCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 + Gold: 100 - type: latheRecipe id: ReagentGrinderMachineCircuitboard @@ -186,6 +195,16 @@ Steel: 100 Glass: 900 +- type: latheRecipe + id: AnalysisComputerCircuitboard + icon: Objects/Misc/module.rsi/cpu_science.png + result: AnalysisComputerCircuitboard + completetime: 4 + materials: + Steel: 100 + Glass: 900 + Gold: 100 + - type: latheRecipe id: CrewMonitoringComputerCircuitboard icon: Objects/Misc/module.rsi/id_mod.png diff --git a/Resources/Prototypes/XenoArch/artifact_effects.yml b/Resources/Prototypes/XenoArch/artifact_effects.yml new file mode 100644 index 0000000000..8452bad16c --- /dev/null +++ b/Resources/Prototypes/XenoArch/artifact_effects.yml @@ -0,0 +1,318 @@ +- type: artifactEffect + id: EffectBadFeeling + targetDepth: 0 + effectHint: artifact-effect-hint-mental + components: + - type: TelepathicArtifact + messages: + - badfeeling-artifact-1 + - badfeeling-artifact-2 + - badfeeling-artifact-3 + - badfeeling-artifact-4 + - badfeeling-artifact-5 + - badfeeling-artifact-6 + - badfeeling-artifact-7 + - badfeeling-artifact-8 + - badfeeling-artifact-9 + - badfeeling-artifact-10 + - badfeeling-artifact-11 + - badfeeling-artifact-12 + - badfeeling-artifact-13 + - badfeeling-artifact-14 + - badfeeling-artifact-15 + drastic: + - badfeeling-artifact-drastic-1 + - badfeeling-artifact-drastic-2 + - badfeeling-artifact-drastic-3 + - badfeeling-artifact-drastic-4 + - badfeeling-artifact-drastic-5 + - badfeeling-artifact-drastic-6 + +- type: artifactEffect + id: EffectGoodFeeling + targetDepth: 0 + effectHint: artifact-effect-hint-mental + components: + - type: TelepathicArtifact + messages: + - goodfeeling-artifact-1 + - goodfeeling-artifact-2 + - goodfeeling-artifact-3 + - goodfeeling-artifact-4 + - goodfeeling-artifact-5 + - goodfeeling-artifact-6 + - goodfeeling-artifact-7 + - goodfeeling-artifact-8 + - goodfeeling-artifact-9 + - goodfeeling-artifact-10 + - goodfeeling-artifact-11 + - goodfeeling-artifact-12 + - goodfeeling-artifact-13 + - goodfeeling-artifact-14 + drastic: + - goodfeeling-artifact-drastic-1 + - goodfeeling-artifact-drastic-2 + - goodfeeling-artifact-drastic-3 + - goodfeeling-artifact-drastic-4 + - goodfeeling-artifact-drastic-5 + - goodfeeling-artifact-drastic-6 + +- type: artifactEffect + id: EffectJunkSpawn + targetDepth: 0 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 10 + consistentSpawns: false + possiblePrototypes: + - FoodPacketSyndiTrash + - FoodPacketSemkiTrash + - FoodPacketBoritosTrash + - FoodPacketCheesieTrash + - FoodPacketChipsTrash + - FoodPacketChocolateTrash + - FoodPacketChowMeinTrash + - FoodPacketEnergyTrash + - FoodPacketPopcornTrash + - FoodPacketRaisinsTrash + - RandomInstruments + - ToySpawner + +- type: artifactEffect + id: EffectLightFlicker + targetDepth: 0 + effectHint: artifact-effect-hint-electrical-interference + components: + - type: LightFlickerArtifact + +- type: artifactEffect + id: EffectPointLight + targetDepth: 0 + components: + - type: PointLight + radius: 2 + energy: 5 + color: "#27153b" + +- type: artifactEffect #bornana + id: EffectBananaSpawn + targetDepth: 1 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 20 + possiblePrototypes: + - FoodBanana + +- type: artifactEffect + id: EffectCold + targetDepth: 1 + effectHint: artifact-effect-hint-consumption + components: + - type: TemperatureArtifact + targetTemp: 150 + +- type: artifactEffect + id: EffectThrow + targetDepth: 1 + effectHint: artifact-effect-hint-environment + components: + - type: ThrowArtifact + +- type: artifactEffect + id: EffectFoamMild + targetDepth: 1 + effectHint: artifact-effect-hint-biochemical + components: + - type: FoamArtifact + reagents: + - Oxygen + - Plasma + - Blood + - SpaceCleaner + - Nutriment + - SpaceLube + - Ethanol + - Mercury + - VentCrud + - WeldingFuel + +- type: artifactEffect + id: EffectInstrumentSpawn + targetDepth: 1 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 5 + possiblePrototypes: + - RandomInstruments + +- type: artifactEffect + id: EffectAngryCarpSpawn + targetDepth: 2 + effectHint: artifact-effect-hint-environment + components: + - type: SpawnArtifact + maxSpawns: 5 + possiblePrototypes: + - MobCarpHolo + - MobCarpMagic + +- type: artifactEffect + id: EffectRadiate + targetDepth: 2 + effectHint: artifact-effect-hint-release + components: + - type: RadiationSource + intensity: 2 + +- type: artifactEffect + id: EffectKnock + targetDepth: 2 + effectHint: artifact-effect-hint-electrical-interference + components: + - type: KnockArtifact + +- type: artifactEffect + id: EffectShatterWindows + targetDepth: 2 + effectHint: artifact-effect-hint-environment + components: + - type: DamageNearbyArtifact + damageChance: 0.75 + whitelist: + tags: + - Window + damage: + types: + Structural: 100 + +- type: artifactEffect + id: EffectMaterialSpawn + targetDepth: 3 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 5 + consistentSpawn: false + possiblePrototypes: + - SheetGlass + - SheetSteel + - SheetPlastic + +- type: artifactEffect + id: EffectShuffle + targetDepth: 3 + effectHint: artifact-effect-hint-displacement + components: + - type: ShuffleArtifact + - type: TelepathicArtifact + range: 7.5 + messages: + - shuffle-artifact-popup + +- type: artifactEffect + id: EffectT3PartsSpawn + targetDepth: 3 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 10 + consistentSpawn: false + possiblePrototypes: + - SuperCapacitorStockPart + - PhasicScanningModuleStockPart + - PicoManipulatorStockPart + - UltraHighPowerMicroLaserStockPart + - SuperMatterBinStockPart + +- type: artifactEffect + id: EffectGas + targetDepth: 3 + effectHint: artifact-effect-hint-environment + components: + - type: GasArtifact + +- type: artifactEffect + id: EffectBlink + targetDepth: 3 + effectHint: artifact-effect-hint-displacement + components: + - type: RandomTeleportArtifact + +- type: artifactEffect + id: EffectDisease + targetDepth: 3 + effectHint: artifact-effect-hint-biochemical + components: + - type: DiseaseArtifact + diseasePrototypes: + - VanAusdallsRobovirus + - OwOnavirus + - BleedersBite + - Ultragigacancer + - MemeticAmirmir + - TongueTwister + - AMIV + +- type: artifactEffect + id: EffectRareMaterialSpawn + targetDepth: 4 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 5 + consistentSpawn: false + possiblePrototypes: + - SilverOre1 + - PlasmaOre1 + - GoldOre1 + - UraniumOre1 + +- type: artifactEffect + id: EffectPowerGen20K + targetDepth: 4 + effectHint: artifact-effect-hint-release + components: + - type: PowerSupplier + supplyRate: 20000 + +- type: artifactEffect + id: EffectHeat + targetDepth: 4 + effectHint: artifact-effect-hint-release + components: + - type: TemperatureArtifact + targetTemp: 450 + +- type: artifactEffect + id: EffectFoamDangerous + targetDepth: 4 + effectHint: artifact-effect-hint-biochemical + components: + - type: FoamArtifact + spreadDuration: 0.5 + duration: 5 + reagents: + - Tritium + - Plasma + - SulfuricAcid + - SpaceDrugs + - Nocturine + - MuteToxin + - Napalm + - CarpoToxin + - ChloralHydrate + - Mold + - Amatoxin + +- type: artifactEffect + id: EffectSingulo + targetDepth: 100 + effectHint: artifact-effect-hint-destruction + components: + - type: SpawnArtifact + maxSpawns: 1 + possiblePrototypes: + - Singularity \ No newline at end of file diff --git a/Resources/Prototypes/XenoArch/artifact_triggers.yml b/Resources/Prototypes/XenoArch/artifact_triggers.yml new file mode 100644 index 0000000000..51e0be3df1 --- /dev/null +++ b/Resources/Prototypes/XenoArch/artifact_triggers.yml @@ -0,0 +1,144 @@ +- type: artifactTrigger + id: TriggerInteraction + targetDepth: 0 + triggerHint: artifact-trigger-hint-physical + components: + - type: ArtifactInteractionTrigger + +- type: artifactTrigger + id: TriggerTimer + targetDepth: 0 + components: + - type: ArtifactTimerTrigger + +- type: artifactTrigger + id: TriggerExamine + targetDepth: 0 + components: + - type: ArtifactExamineTrigger + +- type: artifactTrigger + id: TriggerAnchor + targetDepth: 0 + triggerHint: artifact-trigger-hint-tool + components: + - type: ArtifactAnchorTrigger + +- type: artifactTrigger + id: TriggerElectricity + targetDepth: 0 + triggerHint: artifact-trigger-hint-electricity + components: + - type: ArtifactElectricityTrigger + - type: PowerConsumer + voltage: Medium + drawRate: 500 + - type: Electrified + requirePower: true + noWindowInTile: true + highVoltageNode: high + mediumVoltageNode: medium + lowVoltageNode: low + - type: NodeContainer + nodes: + medium: + !type:CableDeviceNode + nodeGroupID: MVPower + # sadly, HVPower and Apc cables doesn't work right now + +- type: artifactTrigger + id: TriggerMusic + targetDepth: 1 + triggerHint: artifact-trigger-hint-music + components: + - type: ArtifactMusicTrigger + +- type: artifactTrigger + id: TriggerBruteDamage + targetDepth: 1 + triggerHint: artifact-trigger-hint-physical + components: + - type: ArtifactDamageTrigger + damageTypes: + - Blunt + - Slash + - Piercing + damageThreshold: 50 + +- type: artifactTrigger + id: TriggerHeat + targetDepth: 1 + triggerHint: artifact-trigger-hint-heat + components: + - type: ArtifactHeatTrigger + +- type: artifactTrigger + id: TriggerWater + targetDepth: 1 + triggerHint: artifact-trigger-hint-water + components: + - type: Reactive + reactions: + - reagents: [ Water ] + methods: [ Touch ] + effects: + - !type:ActivateArtifact + +- type: artifactTrigger + id: TriggerDeath + targetDepth: 2 + triggerHint: artifact-trigger-hint-death + components: + - type: ArtifactDeathTrigger + +- type: artifactTrigger + id: TriggerMagnet + targetDepth: 2 + triggerHint: artifact-trigger-hint-magnet + components: + - type: ArtifactMagnetTrigger + +- type: artifactTrigger + id: TriggerLowPressure + targetDepth: 2 + triggerHint: artifact-trigger-hint-pressure + components: + - type: ArtifactPressureTrigger + minPressureThreshold: 50 + +- type: artifactTrigger + id: TriggerHighDamage + targetDepth: 3 + triggerHint: artifact-trigger-hint-physical + components: + - type: ArtifactDamageTrigger + damageThreshold: 500 #make it go boom or w/e + +- type: artifactTrigger + id: TriggerRadiation + targetDepth: 3 + triggerHint: artifact-trigger-hint-radiation + components: + - type: ArtifactDamageTrigger + damageTypes: + - Radiation + damageThreshold: 100 + - type: RadiationReceiver + +- type: artifactTrigger + id: TriggerHighPressure + targetDepth: 3 + triggerHint: artifact-trigger-hint-pressure + components: + - type: ArtifactPressureTrigger + maxPressureThreshold: 385 + +- type: artifactTrigger + id: TriggerGas + targetDepth: 3 + triggerHint: artifact-trigger-hint-gas + components: + - type: ArtifactGasTrigger + +#don't add in new targetdepth values until you have a few +#or else it will skew heavily towards a few options. \ No newline at end of file diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/icon.png b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c034a187f34bef96e631160545504451d872815e GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}?>t=`Ln2z= zPBY{>L9vo0hsn(7S`+bHF0f%_NXcZXyjFYulFQ$J`W1|+Iw2W-e+_(qK4bP0l+XkKKnZ>N literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json new file mode 100644 index 0000000000..413b7c38ed --- /dev/null +++ b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by EmoGarbage404", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "unshaded" + } + ] +} diff --git a/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/unshaded.png b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..795efc3d38bebbff0c226596d240515e08947710 GIT binary patch literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL|>AJY5_^B3j>G z+{oLYAi{FtvIScq6I&s3wAS+^p7sSPOkx+i-0BJgJ#^kp6=#a!6A%=1y7TC&^7b=l z6!-tq+nKuY(5(e=Ps-X`u53yA6l27oFehPEt6-`@ZDF1N5% zN9C#2F0EpRLV<;9%3#`IeUq$-*R+h?d-v=Se$d9~5I8+l^78M7EcjN#*w?(3E-BUq z4@psqhx{{qy&LwfwEm`>mUX2y=!sAYa%{Oz9U4oacBON=kDF7CRc%rpL|I5LN;d?p z48gVgjK|7Zr|@+l zhKv5$0h)RkG_T+-Z)2ya&XDUOO`oByD+nvNtjlN9g zang?ebGu&Nyq8tJm3B+yg7-jtt~%=7NS5?{qE-2$H`xf5C&`%c zoH@1Dl+X^DiYY%hw>+n@*6ET1UVRj9%m_C3xl$O<{HIN9 z>aMHi;tEjdzZ&@#X|G&XRbfQ|{&-lXWO?Aujb2nhP2^m&a$iP`DYnL3%DA4sjMrXM zednmP_mB5#1&!_sxiqb$N}D&^h3{HXT~8>!e)>r)ozufwHK~gjLwqSOJUd$vY?VPG zCbGeqjD~cCYonvKcw_PC&1aK5Jhl^}6@-ScmL}pSKVIQBHtr6@BM}yRj#|~K^ znBQ}QP%{kw?%NJ;&V~~QFcBfo>aGd)FjKK_rw>%{Y62Il| ztIciyQx=v34YxC?HieO&iw$2MQUL|igzq2!+OuooU^_<#$uhazaAe0)U(2;CT+&H&s<^ltd2*SBzJQ!~fEk^K?N_$4GJt^<4c!Y#B6_M-5}R%Qk`&MnYb2jxVV1_r#e(ewZ2jp-}SI zx=?Uzr-gU_lp2^?@hr0)(YltD#d>z+md!OQ53rOm*Rr}~2s#g+7>&VsS{z9WqLetu zq#gZy=9{BU*H6!sHYH}&CeF+tp(yrqD>vc&451Oi#N6+KDQvo)kv4`mCnjqAU)owG zC88mBCOq0K`zn`dm$dHAaOgaL$Bb2MIAz+OM&dwU2p>Bfx{CqYS91VTWOjYY2gugh z5YI3JZJ0M2d-l^}lCVSK5BrLUK8}U*T;}C#`R}Jk+l@S66mKn@HvI`FQES+>8&*-% z?a;jMua`n-2htJ23P^&!7beUWTh`1T*2k)QdNh=J^C~T3VhsvALs#86^3RVRBl`I^oW`{wL!gA#kW zq5luSLqMbhKGwKI7L9yDs`G%jTO@foTLx1%+kQ3SyM3pU8-jt7!I85ElG8b?JZmHN zAK7pKt3Le5&x3l-K?`nlkSiUDU!>GSVb`=;%?jFnq{0$eL$dD15=Kc6>?&C8Rz+;s z!87qO2~-ZK7ct7F-EwcRp=q^CvWpKjHHp{mA%T(y&Kh89YEyh<56UZXNUUf)qk5xs zvCZ>KLmT`J)ECQaQEIAi>IgEh9j-wtg_J7z6!a+|7ziH$kJM^k&ju;Wl%gLH-=>cH zfX_r{GI?y`ixtV@G&;A3rNXpIiU%pDA_H46Zx!vK>ZpnG=Q3tk#u-Nh;?A8q;L-v5 zmpXlouCAZ)$%c<^5AxY)V$Y^`j->JXe93Z!$V({4Jc(B_e4ZmYiML&IwKHlnaFr1u zyo*Nz|Fk^6w3kywfrPl3Ic`NRgn1gT)MfWIBBREF-ub(GQmpgxi3nDKZ}$FE$qd(P zYvdOLXk*9V&_Lk`VFC2Pc*ss+611JyhD?*zpVmVd-}kVPezZbl`POl+BA&%m+&wkU zy{Z6jQc||aYp5;m1#m{4q~tk-J`P-7S)$Nml43#Un+U+pQk3QXdw&B8IF^F| literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Machines/computers.rsi/meta.json b/Resources/Textures/Structures/Machines/computers.rsi/meta.json index 653238d34c..1b4bde5f71 100644 --- a/Resources/Textures/Structures/Machines/computers.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/computers.rsi/meta.json @@ -153,6 +153,48 @@ ] ] }, + { + "name": "artifact", + "directions": 4, + "delays": [ + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + ] + }, { "name": "atmos_key", "directions": 4