XenoArch [Science Overhaul] (#12204)
* multi-node xeno artifacts * refactor existing artifact effects * more tweaks to generation * more shit plus fix tests * more generation stuff plus threat levels * doink * now make it build * defer the artifact activation to not cause errors also pricing * some changes * all of the yaml + ui stuff for artifact analyzer * machine linking and starting to make the ui functional * artifact analyzer display * a shit ton of artifact analyzer stuff * more changes; making destroy work properly; progress bar tweaks * getting shit going! ALL RIGHT * small tweaks that didn't help much * Komm susser todd: the end of analysis * recipes and hints and ui, oh my! * add some in-game sources gotta prepare for day 1 launch * node data + ditch random seed in place of id * bunch of triggers * finish off the last few triggers * implement machine examine verb * knock, flicker, blink, throw * shatter, foam, shuffle, heat * fix all the shit i broke * *some* of these have to be good, no? 25 effects * callin' it there for effects * comments + reword some trigger hints * don't mind this little commit here * byref event * fix brokey node entry * fix low pressure trigger * mirror review plus fixing 0x40's bug also the throw artifact threw incorrectly * randomize the event message a teeny bit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Activecomp used for tracking artifact analyzers that are currently
|
||||
/// in the process of scanning an artifact.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveArtifactAnalyzerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// When did the scanning start?
|
||||
/// </summary>
|
||||
[DataField("startTime", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
public TimeSpan StartTime;
|
||||
|
||||
/// <summary>
|
||||
/// What is being scanned?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid Artifact;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for tracking artifacts that are currently
|
||||
/// being scanned by <see cref="ActiveArtifactAnalyzerComponent"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveScannedArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The scanner that is scanning this artifact
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid Scanner;
|
||||
|
||||
/// <summary>
|
||||
/// The sound that plays when the scan fails
|
||||
/// </summary>
|
||||
public readonly SoundSpecifier ScanFailureSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The console that is used for artifact analysis
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class AnalysisConsoleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The analyzer entity the console is linked.
|
||||
/// Can be null if not linked.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? AnalyzerEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The machine linking port for the analyzer
|
||||
/// </summary>
|
||||
[DataField("linkingPort", customTypeSerializer: typeof(PrototypeIdSerializer<TransmitterPortPrototype>))]
|
||||
public readonly string LinkingPort = "ArtifactAnalyzerSender";
|
||||
|
||||
/// <summary>
|
||||
/// The sound played when an artifact is destroyed.
|
||||
/// </summary>
|
||||
[DataField("destroySound")]
|
||||
public SoundSpecifier DestroySound = new SoundPathSpecifier("/Audio/Effects/radpulse11.ogg");
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// A machine that is combined and linked to the <see cref="AnalysisConsoleComponent"/>
|
||||
/// in order to analyze and destroy artifacts.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactAnalyzerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long it takes to analyze an artifact
|
||||
/// </summary>
|
||||
[DataField("analysisDuration", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
public TimeSpan AnalysisDuration = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// A mulitplier on the duration of analysis.
|
||||
/// Used for machine upgrading.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float AnalysisDurationMulitplier = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The machine part that modifies analysis duration.
|
||||
/// </summary>
|
||||
[DataField("machinePartAnalysisDuration", customTypeSerializer: typeof(PrototypeIdSerializer<MachinePartPrototype>))]
|
||||
public string MachinePartAnalysisDuration = "ScanningModule";
|
||||
|
||||
/// <summary>
|
||||
/// The modifier raised to the part rating to determine the duration multiplier.
|
||||
/// </summary>
|
||||
[DataField("partRatingAnalysisDurationMultiplier")]
|
||||
public float PartRatingAnalysisDurationMultiplier = 0.75f;
|
||||
|
||||
/// <summary>
|
||||
/// The corresponding console entity.
|
||||
/// Can be null if not linked.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Console;
|
||||
|
||||
/// <summary>
|
||||
/// All of the valid artifacts currently touching the analyzer.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<EntityUid> 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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Equipment.Components;
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Suppress artifact activation, when entity is placed inside this container.
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ActiveScannedArtifactComponent, MoveEvent>(OnScannedMoved);
|
||||
SubscribeLocalEvent<ActiveScannedArtifactComponent, ArtifactActivatedEvent>(OnArtifactActivated);
|
||||
|
||||
SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentStartup>(OnAnalyzeStart);
|
||||
SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, ComponentShutdown>(OnAnalyzeEnd);
|
||||
SubscribeLocalEvent<ActiveArtifactAnalyzerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, UpgradeExamineEvent>(OnUpgradeExamine);
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, RefreshPartsEvent>(OnRefreshParts);
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, StartCollideEvent>(OnCollide);
|
||||
SubscribeLocalEvent<ArtifactAnalyzerComponent, EndCollideEvent>(OnEndCollide);
|
||||
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, NewLinkEvent>(OnNewLink);
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, PortDisconnectedEvent>(OnPortDisconnected);
|
||||
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleServerSelectionMessage>(OnServerSelectionMessage);
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleScanButtonPressedMessage>(OnScanButton);
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, AnalysisConsoleDestroyButtonPressedMessage>(OnDestroyButton);
|
||||
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerSelectedMessage>((e,c,_) => UpdateUserInterface(e,c),
|
||||
after: new []{typeof(ResearchSystem)});
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, ResearchClientServerDeselectedMessage>((e,c,_) => UpdateUserInterface(e,c),
|
||||
after: new []{typeof(ResearchSystem)});
|
||||
SubscribeLocalEvent<AnalysisConsoleComponent, BeforeActivatableUIOpenEvent>((e,c,_) => UpdateUserInterface(e,c));
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var (active, scan) in EntityQuery<ActiveArtifactAnalyzerComponent, ArtifactAnalyzerComponent>())
|
||||
{
|
||||
if (scan.Console != null)
|
||||
UpdateUserInterface(scan.Console.Value);
|
||||
|
||||
if (_timing.CurTime - active.StartTime < (scan.AnalysisDuration * scan.AnalysisDurationMulitplier))
|
||||
continue;
|
||||
|
||||
FinishScan(scan.Owner, scan, active);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current scan on the artifact analyzer
|
||||
/// </summary>
|
||||
/// <param name="uid">The analyzer being reset</param>
|
||||
/// <param name="component"></param>
|
||||
[PublicAPI]
|
||||
public void ResetAnalyzer(EntityUid uid, ArtifactAnalyzerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.LastAnalyzedArtifact = null;
|
||||
UpdateAnalyzerInformation(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Goes through the current contacts on
|
||||
/// the analyzer and returns a valid artifact
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns></returns>
|
||||
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<ArtifactComponent>).ToHashSet();
|
||||
return validEnts.FirstOrNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current scan information based on
|
||||
/// the last artifact that was scanned.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
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<ArtifactComponent>(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<ArtifactAnalyzerComponent>(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<ArtifactAnalyzerComponent>(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<ArtifactAnalyzerComponent>(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<ResearchClientComponent>(uid, out var client) && client.ConnectedToServer;
|
||||
|
||||
var scanning = TryComp<ActiveArtifactAnalyzerComponent>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// opens the server selection menu.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args)
|
||||
{
|
||||
_ui.TryOpen(uid, ResearchClientUiKey.Key, (IPlayerSession) args.Session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts scanning the artifact.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnScanButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleScanButtonPressedMessage args)
|
||||
{
|
||||
if (component.AnalyzerEntity == null)
|
||||
return;
|
||||
|
||||
if (HasComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity))
|
||||
return;
|
||||
|
||||
var ent = GetArtifactForAnalysis(component.AnalyzerEntity);
|
||||
if (ent == null)
|
||||
return;
|
||||
|
||||
var activeComp = EnsureComp<ActiveArtifactAnalyzerComponent>(component.AnalyzerEntity.Value);
|
||||
activeComp.StartTime = _timing.CurTime;
|
||||
activeComp.Artifact = ent.Value;
|
||||
|
||||
var activeArtifact = EnsureComp<ActiveScannedArtifactComponent>(ent.Value);
|
||||
activeArtifact.Scanner = component.AnalyzerEntity.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// destroys the artifact and updates the server points
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnDestroyButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleDestroyButtonPressedMessage args)
|
||||
{
|
||||
if (!TryComp<ResearchClientComponent>(uid, out var client) || client.Server == null || component.AnalyzerEntity == null)
|
||||
return;
|
||||
|
||||
var entToDestroy = GetArtifactForAnalysis(component.AnalyzerEntity);
|
||||
if (entToDestroy == null)
|
||||
return;
|
||||
|
||||
if (TryComp<ArtifactAnalyzerComponent>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels scans if the artifact changes nodes (is activated) during the scan.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnArtifactActivated(EntityUid uid, ActiveScannedArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
CancelScan(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to make sure that the currently scanned artifact isn't moved off of the scanner
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnScannedMoved(EntityUid uid, ActiveScannedArtifactComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (!TryComp<ArtifactAnalyzerComponent>(component.Scanner, out var analyzer))
|
||||
return;
|
||||
|
||||
if (analyzer.Contacts.Contains(uid))
|
||||
return;
|
||||
|
||||
CancelScan(uid, component, analyzer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the current scan
|
||||
/// </summary>
|
||||
/// <param name="artifact">The artifact being scanned</param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="analyzer">The artifact analyzer component</param>
|
||||
[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<ActiveArtifactAnalyzerComponent>(component.Scanner);
|
||||
if (analyzer.Console != null)
|
||||
UpdateUserInterface(analyzer.Console.Value);
|
||||
|
||||
RemCompDeferred(artifact, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the current scan.
|
||||
/// </summary>
|
||||
/// <param name="uid">The analyzer that is scanning</param>
|
||||
/// <param name="component"></param>
|
||||
/// <param name="active"></param>
|
||||
[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<ActiveScannedArtifactComponent>(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<ArtifactComponent>(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<ArtifactComponent>(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<ApcPowerReceiverComponent>(uid, out var powa))
|
||||
powa.NeedsPower = true;
|
||||
|
||||
if (TryComp<AmbientSoundComponent>(uid, out var ambientSound))
|
||||
{
|
||||
ambientSound.Enabled = true;
|
||||
Dirty(ambientSound);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryComp<ApcPowerReceiverComponent>(uid, out var powa))
|
||||
powa.NeedsPower = false;
|
||||
|
||||
if (TryComp<AmbientSoundComponent>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Artifacts go from 2k to 4k, 1.5k net profit (considering the container price
|
||||
/// </summary>
|
||||
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<StaticPriceComponent>(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<StaticPriceComponent>(args.Entity, out var price))
|
||||
{
|
||||
price.Price /= ContainedArtifactModifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Should artifact pick a random trigger on startup?
|
||||
/// The artifact's node tree.
|
||||
/// </summary>
|
||||
[DataField("randomTrigger")]
|
||||
public bool RandomTrigger = true;
|
||||
[ViewVariables]
|
||||
public ArtifactTree? NodeTree;
|
||||
|
||||
/// <summary>
|
||||
/// List of all possible triggers activations.
|
||||
/// Should be same as components names.
|
||||
/// The current node the artifact is on.
|
||||
/// </summary>
|
||||
[DataField("possibleTriggers")]
|
||||
public string[] PossibleTriggers = {
|
||||
"ArtifactInteractionTrigger",
|
||||
"ArtifactGasTrigger",
|
||||
"ArtifactHeatTrigger",
|
||||
"ArtifactElectricityTrigger",
|
||||
};
|
||||
[ViewVariables]
|
||||
public ArtifactNode? CurrentNode;
|
||||
|
||||
#region Node Tree Gen
|
||||
/// <summary>
|
||||
/// Minimum number of nodes to generate, inclusive
|
||||
/// </summary>
|
||||
[DataField("nodesMin")]
|
||||
public int NodesMin = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown time between artifact activations (in seconds).
|
||||
/// Maximum number of nodes to generate, exclusive
|
||||
/// </summary>
|
||||
[DataField("timer")]
|
||||
[DataField("nodesMax")]
|
||||
public int NodesMax = 9;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown time between artifact activations (in seconds).
|
||||
/// </summary>
|
||||
[DataField("timer", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public double CooldownTime = 10;
|
||||
public TimeSpan CooldownTime = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IsSuppressed;
|
||||
|
||||
/// <summary>
|
||||
/// The last time the artifact was activated.
|
||||
/// </summary>
|
||||
[DataField("lastActivationTime", customTypeSerializer: typeof(TimespanSerializer))]
|
||||
public TimeSpan LastActivationTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A tree of nodes.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed class ArtifactTree
|
||||
{
|
||||
/// <summary>
|
||||
/// The first node of the tree
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ArtifactNode StartNode = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Every node contained in the tree
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public readonly List<ArtifactNode> AllNodes = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single "node" of an artifact that contains various data about it.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed class ArtifactNode : ICloneable
|
||||
{
|
||||
/// <summary>
|
||||
/// A numeric id corresponding to each node. used for display purposes
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Id;
|
||||
|
||||
/// <summary>
|
||||
/// how "deep" into the node tree. used for generation and price/value calculations
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Depth = 0;
|
||||
|
||||
/// <summary>
|
||||
/// A list of surrounding nodes. Used for tree traversal
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public List<ArtifactNode> Edges = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the node has been entered
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Discovered = false;
|
||||
|
||||
/// <summary>
|
||||
/// The trigger for the node
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ArtifactTriggerPrototype Trigger = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the node has been triggered
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Triggered = false;
|
||||
|
||||
/// <summary>
|
||||
/// The effect when the node is activated
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ArtifactEffectPrototype Effect = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Used for storing cumulative information about nodes
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Dictionary<string, object> NodeData = new();
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return new ArtifactNode
|
||||
{
|
||||
Id = Id,
|
||||
Depth = Depth,
|
||||
Edges = Edges,
|
||||
Discovered = Discovered,
|
||||
Trigger = Trigger,
|
||||
Triggered = Triggered,
|
||||
Effect = Effect,
|
||||
NodeData = NodeData
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Generate an Artifact tree with fully developed nodes.
|
||||
/// </summary>
|
||||
/// <param name="tree">The tree being generated.</param>
|
||||
/// <param name="nodeAmount">The amount of nodes it has.</param>
|
||||
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<ArtifactNode> { new() };
|
||||
tree.StartNode = uninitializedNodes.First(); //the first node
|
||||
|
||||
while (uninitializedNodes.Any())
|
||||
{
|
||||
GenerateNode(ref uninitializedNodes, ref tree, nodeAmount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate an individual node on the tree.
|
||||
/// </summary>
|
||||
private void GenerateNode(ref List<ArtifactNode> 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<ArtifactTriggerPrototype>().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<ArtifactEffectPrototype>().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);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
private Dictionary<int, float> GetDepthWeights(IEnumerable<int> depths, int targetDepth)
|
||||
{
|
||||
var weights = new Dictionary<int, float>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a weighted random system to get a random depth.
|
||||
/// </summary>
|
||||
private int GetRandomTargetDepth(Dictionary<int, float> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enter a node: attach the relevant components
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exit a node: remove the relevant components.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<ArtifactComponent, MapInitEvent>(OnInit);
|
||||
SubscribeLocalEvent<ArtifactComponent, PriceCalculationEvent>(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)
|
||||
/// <summary>
|
||||
/// Calculates the price of an artifact based on
|
||||
/// how many nodes have been unlocked/triggered
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// General balancing (for fully unlocked artifacts):
|
||||
/// Simple (1-2 Nodes): 1-2K
|
||||
/// Medium (5-8 Nodes): 6-7K
|
||||
/// Complex (7-12 Nodes): 10-11K
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates how many research points the artifact is worht
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Rebalance this shit at some point. Definitely OP.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomize a given artifact.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to activate the artifact
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="component"></param>
|
||||
/// <returns></returns>
|
||||
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)
|
||||
/// <summary>
|
||||
/// Forces an artifact to activate
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="component"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try and get a data object from a node
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity you're getting the data from</param>
|
||||
/// <param name="key">The data's key</param>
|
||||
/// <param name="data">The data you are trying to get.</param>
|
||||
/// <param name="component"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public bool TryGetNodeData<T>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node data to a certain value
|
||||
/// </summary>
|
||||
/// <param name="uid">The artifact</param>
|
||||
/// <param name="key">The key being set</param>
|
||||
/// <param name="value">The value it's being set to</param>
|
||||
/// <param name="component"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When activated, damages nearby entities.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class DamageNearbyArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius of entities that will be affected
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
public float Radius = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// A whitelist for filtering certain damage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: The component portion, since it uses an array, does not work currently.
|
||||
/// </remarks>
|
||||
[DataField("whitelist")]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// The damage that is applied
|
||||
/// </summary>
|
||||
[DataField("damage", required: true)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The chance that damage is applied to each individual entity
|
||||
/// </summary>
|
||||
[DataField("damageChance")]
|
||||
public float DamageChance = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this should ignore resistances for the damage
|
||||
/// </summary>
|
||||
[DataField("ignoreResistances")]
|
||||
public bool IgnoreResistances;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Disease;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
/// <summary>
|
||||
@@ -8,10 +9,15 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
public sealed class DiseaseArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Disease the artifact will spawn
|
||||
/// If empty, picks a random one from its list
|
||||
/// The diseases that the artifact can use.
|
||||
/// </summary>
|
||||
[DataField("diseasePrototype", customTypeSerializer: typeof(PrototypeIdListSerializer<DiseasePrototype>))]
|
||||
public List<string> DiseasePrototypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Disease the artifact will spawn
|
||||
/// Picks a random one from its list
|
||||
/// </summary>
|
||||
[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
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("range"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Range = 5f;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Generates foam from the artifact when activated
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class FoamArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of reagents that will randomly be picked from
|
||||
/// to choose the foam reagent
|
||||
/// </summary>
|
||||
[DataField("reagents", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ReagentPrototype>))]
|
||||
public List<string> Reagents = new();
|
||||
|
||||
/// <summary>
|
||||
/// The foam reagent
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? SelectedReagent;
|
||||
|
||||
/// <summary>
|
||||
/// How long does the foam last?
|
||||
/// </summary>
|
||||
[DataField("duration")]
|
||||
public float Duration = 10;
|
||||
|
||||
/// <summary>
|
||||
/// How much reagent is in the foam?
|
||||
/// </summary>
|
||||
[DataField("reagentAmount")]
|
||||
public float ReagentAmount = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum radius of foam spawned
|
||||
/// </summary>
|
||||
[DataField("minFoamAmount")]
|
||||
public int MinFoamAmount = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum radius of foam spawned
|
||||
/// </summary>
|
||||
[DataField("maxFoamAmount")]
|
||||
public int MaxFoamAmount = 6;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes for each tile of foam to spawn
|
||||
/// </summary>
|
||||
[DataField("spreadDuration")]
|
||||
public float SpreadDuration = 1;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public sealed class GasArtifactComponent : Component
|
||||
/// List of possible activation gases to pick on startup.
|
||||
/// </summary>
|
||||
[DataField("possibleGas")]
|
||||
public Gas[] PossibleGases =
|
||||
public List<Gas> PossibleGases = new()
|
||||
{
|
||||
Gas.Oxygen,
|
||||
Gas.Plasma,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for using the "knock" spell when the artifact is activated
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class KnockArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The range of the spell
|
||||
/// </summary>
|
||||
[DataField("knockRange")]
|
||||
public float KnockRange = 4f;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Flickers all the lights within a certain radius.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class LightFlickerArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Lights within this radius will be flickered on activation
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
public float Radius = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The chance that the light will flicker
|
||||
/// </summary>
|
||||
[DataField("flickerChance")]
|
||||
public float FlickerChance = 0.75f;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Spawn RadiationPulse when artifact activated.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class RadiateArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Radiation pulse prototype to spawn.
|
||||
/// </summary>
|
||||
[DataField("pulsePrototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string PulsePrototype = "RadiationPulse";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When activated, will teleport the artifact
|
||||
/// to a random position within a certain radius
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class RandomTeleportArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The max distance that the artifact will teleport.
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 7.5f;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When activated, will shuffle the position of all players
|
||||
/// within a certain radius.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ShuffleArtifactComponent : Component
|
||||
{
|
||||
[DataField("radius")]
|
||||
public float Radius = 7.5f;
|
||||
}
|
||||
@@ -11,21 +11,35 @@ namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
[RegisterComponent]
|
||||
public sealed class SpawnArtifactComponent : Component
|
||||
{
|
||||
[DataField("random")]
|
||||
public bool RandomPrototype = true;
|
||||
|
||||
/// <summary>
|
||||
/// The list of possible prototypes to spawn that it picks from.
|
||||
/// </summary>
|
||||
[DataField("possiblePrototypes", customTypeSerializer:typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> PossiblePrototypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// The prototype it selected to spawn.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// The range around the artifact that it will spawn the entity
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of times the spawn will occur
|
||||
/// </summary>
|
||||
[DataField("maxSpawns")]
|
||||
public int MaxSpawns = 20;
|
||||
|
||||
public int SpawnsCount = 0;
|
||||
/// <summary>
|
||||
/// Whether or not the artifact spawns the same entity every time
|
||||
/// or picks through the list each time.
|
||||
/// </summary>
|
||||
[DataField("consistentSpawn")]
|
||||
public bool ConsistentSpawn = true;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public sealed class TelepathicArtifactComponent : Component
|
||||
/// </summary>
|
||||
[DataField("messages")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string[] Messages = default!;
|
||||
public List<string> Messages = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Loc string ids of telepathic messages (spooky version).
|
||||
@@ -21,7 +21,7 @@ public sealed class TelepathicArtifactComponent : Component
|
||||
/// </summary>
|
||||
[DataField("drastic")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string[] DrasticMessages = default!;
|
||||
public List<string>? DrasticMessages;
|
||||
|
||||
/// <summary>
|
||||
/// Probability to pick drastic version of message.
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
[DataField("effectAdjacent")]
|
||||
public bool EffectAdjacentTiles = true;
|
||||
[DataField("affectAdjacent")]
|
||||
public bool AffectAdjacentTiles = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Throws all nearby entities backwards.
|
||||
/// Also pries nearby tiles.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ThrowArtifactComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How close do you have to be to get yeeted?
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// How likely is it that an individual tile will get pried?
|
||||
/// </summary>
|
||||
[DataField("tilePryChance")]
|
||||
public float TilePryChance = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// How strongly does stuff get thrown?
|
||||
/// </summary>
|
||||
[DataField("throwStrength"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ThrowStrength = 5f;
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<DamageNearbyArtifactComponent, ArtifactActivatedEvent>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string> ArtifactDiseases = new[]
|
||||
{
|
||||
"VanAusdallsRobovirus",
|
||||
"OwOnavirus",
|
||||
"BleedersBite",
|
||||
"Ultragigacancer",
|
||||
"MemeticAmirmir",
|
||||
"TongueTwister",
|
||||
"AMIV"
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DiseaseArtifactComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<DiseaseArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<DiseaseArtifactComponent, ArtifactActivatedEvent>(OnActivate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure this artifact is assigned a disease
|
||||
/// </summary>
|
||||
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<DiseasePrototype>(diseaseName, out var disease))
|
||||
{
|
||||
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FoamArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<FoamArtifactComponent, ArtifactActivatedEvent>(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);
|
||||
}
|
||||
}
|
||||
@@ -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<GasArtifactComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<GasArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<GasArtifactComponent, ArtifactActivatedEvent>(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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<KnockArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, KnockArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var ev = new KnockSpellEvent
|
||||
{
|
||||
Performer = uid,
|
||||
Range = component.KnockRange
|
||||
};
|
||||
RaiseLocalEvent(ev);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class LightFlickerArtifactSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<LightFlickerArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, LightFlickerArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var lights = GetEntityQuery<PoweredLightComponent>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<RadiateArtifactComponent, ArtifactActivatedEvent>(OnActivate);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, RadiateArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var transform = Transform(uid);
|
||||
EntityManager.SpawnEntity(component.PulsePrototype, transform.Coordinates);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class RandomTeleportArtifactSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<RandomTeleportArtifactComponent, ArtifactActivatedEvent>(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));
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ShuffleArtifactComponent, ArtifactActivatedEvent>(OnActivated);
|
||||
}
|
||||
|
||||
private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, ArtifactActivatedEvent args)
|
||||
{
|
||||
var mobState = GetEntityQuery<MobStateComponent>();
|
||||
|
||||
List<EntityCoordinates> allCoords = new();
|
||||
List<TransformComponent> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<SpawnArtifactComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SpawnArtifactComponent, ArtifactNodeEnteredEvent>(OnNodeEntered);
|
||||
SubscribeLocalEvent<SpawnArtifactComponent, ArtifactActivatedEvent>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string> 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ThrowArtifactComponent, ArtifactActivatedEvent>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,20 @@ public sealed class ArtifactActivatedEvent : EntityEventArgs
|
||||
/// </summary>
|
||||
public EntityUid? Activator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force to randomize artifact triggers.
|
||||
/// </summary>
|
||||
public sealed class ArtifactNodeEnteredEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// An entity-specific seed that can be used to
|
||||
/// generate random values.
|
||||
/// </summary>
|
||||
public readonly int RandomSeed;
|
||||
|
||||
public ArtifactNodeEnteredEvent(int randomSeed)
|
||||
{
|
||||
RandomSeed = randomSeed;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Force to randomize artifact triggers.
|
||||
/// </summary>
|
||||
public sealed class RandomizeTriggerEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when an artifact is anchored
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not every trigger can be a winner
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactAnchorTriggerComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when a certain threshold of damage of certain types is reached
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactDamageTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// What damage types are accumulated for the trigger?
|
||||
/// </summary>
|
||||
[DataField("damageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string>? DamageTypes;
|
||||
|
||||
/// <summary>
|
||||
/// What threshold has to be reached before it is activated?
|
||||
/// </summary>
|
||||
[DataField("damageThreshold", required: true)]
|
||||
public float DamageThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// How much damage has been accumulated on the artifact so far
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float AccumulatedDamage = 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when a nearby entity dies
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactDeathTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How close to the death the artifact has to be for it to trigger.
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 15f;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when the artifact is examined.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactExamineTriggerComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public sealed class ArtifactGasTriggerComponent : Component
|
||||
/// List of possible activation gases to pick on startup.
|
||||
/// </summary>
|
||||
[DataField("possibleGas")]
|
||||
public Gas[] PossibleGases =
|
||||
public List<Gas> PossibleGases = new()
|
||||
{
|
||||
Gas.Oxygen,
|
||||
Gas.Plasma,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when the salvage magnet is activated
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactMagnetTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// how close to the magnet do you have to be?
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 40f;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when an instrument is played nearby
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactMusicTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// how close does the artifact have to be to the instrument to activate
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
public float Range = 5;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when a certain pressure threshold is hit
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ArtifactPressureTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The lower-end pressure threshold
|
||||
/// </summary>
|
||||
[DataField("minPressureThreshold")]
|
||||
public float? MinPressureThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// The higher-end pressure threshold
|
||||
/// </summary>
|
||||
[DataField("maxPressureThreshold")]
|
||||
public float? MaxPressureThreshold;
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ArtifactAnchorTriggerComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
|
||||
}
|
||||
|
||||
private void OnAnchorStateChanged(EntityUid uid, ArtifactAnchorTriggerComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (args.Detaching)
|
||||
return;
|
||||
|
||||
_artifact.TryActivateArtifact(uid);
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ArtifactDamageTriggerComponent, DamageChangedEvent>(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);
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(MobStateChangedEvent ev)
|
||||
{
|
||||
if (ev.CurrentMobState != DamageState.Dead)
|
||||
return;
|
||||
|
||||
var deathXform = Transform(ev.Entity);
|
||||
|
||||
var toActivate = new List<ArtifactDeathTriggerComponent>();
|
||||
foreach (var (trigger, xform) in EntityQuery<ArtifactDeathTriggerComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,18 @@ public sealed class ArtifactElectricityTriggerSystem : EntitySystem
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var query = EntityManager.EntityQuery<ArtifactElectricityTriggerComponent, PowerConsumerComponent, ArtifactComponent>();
|
||||
foreach (var (trigger, power, artifact) in query)
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, power, artifact) in EntityQuery<ArtifactElectricityTriggerComponent, PowerConsumerComponent, ArtifactComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ArtifactExamineTriggerComponent, ExaminedEvent>(OnExamine);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, ArtifactExamineTriggerComponent component, ExaminedEvent args)
|
||||
{
|
||||
_artifact.TryActivateArtifact(uid);
|
||||
}
|
||||
}
|
||||
@@ -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<ArtifactGasTriggerComponent, RandomizeTriggerEvent>(OnRandomizeTrigger);
|
||||
SubscribeLocalEvent<ArtifactGasTriggerComponent, ArtifactNodeEnteredEvent>(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<ArtifactGasTriggerComponent, TransformComponent>();
|
||||
foreach (var (trigger, transform) in query)
|
||||
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, artifact, transform) in EntityQuery<ArtifactGasTriggerComponent, ArtifactComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ArtifactHeatTriggerComponent, TransformComponent, ArtifactComponent>();
|
||||
foreach (var (trigger, transform, artifact) in query)
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, transform, artifact) in EntityQuery<ArtifactHeatTriggerComponent, TransformComponent, ArtifactComponent>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Server.Salvage;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class ArtifactMagnetTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ArtifactSystem _artifact = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SalvageMagnetActivatedEvent>(OnMagnetActivated);
|
||||
}
|
||||
|
||||
private void OnMagnetActivated(SalvageMagnetActivatedEvent ev)
|
||||
{
|
||||
var magXform = Transform(ev.Magnet);
|
||||
|
||||
var toActivate = new List<EntityUid>();
|
||||
foreach (var (artifact, xform) in EntityQuery<ArtifactMagnetTriggerComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles activating an artifact when music is playing nearby
|
||||
/// </summary>
|
||||
public sealed class ArtifactMusicTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ArtifactSystem _artifact = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var artifactQuery = EntityQuery<ArtifactMusicTriggerComponent, TransformComponent>().ToArray();
|
||||
if (!artifactQuery.Any())
|
||||
return;
|
||||
|
||||
List<EntityUid> toActivate = new();
|
||||
|
||||
//assume that there's more instruments than artifacts
|
||||
foreach (var activeinstrument in EntityQuery<ActiveInstrumentComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// This handles activation upon certain pressure thresholds.
|
||||
/// </summary>
|
||||
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<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, artifact, transform) in EntityQuery<ArtifactPressureTriggerComponent, ArtifactComponent, TransformComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ArtifactTimerTriggerComponent, ComponentStartup>(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<ArtifactTimerTriggerComponent, ArtifactComponent>();
|
||||
foreach (var (trigger, artifact) in query)
|
||||
List<ArtifactComponent> toUpdate = new();
|
||||
foreach (var (trigger, artifact) in EntityQuery<ArtifactTimerTriggerComponent, ArtifactComponent>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user