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 0000000000..df3145a08a Binary files /dev/null and b/Resources/Audio/Effects/Lightning/lightningbolt.ogg differ 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 0000000000..7d1a737fc2 Binary files /dev/null and b/Resources/Audio/Machines/scan_finish.ogg differ diff --git a/Resources/Audio/Machines/scan_loop.ogg b/Resources/Audio/Machines/scan_loop.ogg new file mode 100644 index 0000000000..bb7155514a Binary files /dev/null and b/Resources/Audio/Machines/scan_loop.ogg differ 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 0000000000..c034a187f3 Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/icon.png differ 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 0000000000..795efc3d38 Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_analyzer.rsi/unshaded.png differ diff --git a/Resources/Textures/Structures/Machines/computers.rsi/artifact.png b/Resources/Textures/Structures/Machines/computers.rsi/artifact.png new file mode 100644 index 0000000000..5fc7c22947 Binary files /dev/null and b/Resources/Textures/Structures/Machines/computers.rsi/artifact.png differ 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