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,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Systems;
|
||||
|
||||
public sealed class SuppressArtifactContainerSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SuppressArtifactContainerComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<SuppressArtifactContainerComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
}
|
||||
|
||||
private void OnInserted(EntityUid uid, SuppressArtifactContainerComponent component, EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (!TryComp(args.Entity, out ArtifactComponent? artifact))
|
||||
return;
|
||||
|
||||
artifact.IsSuppressed = true;
|
||||
}
|
||||
|
||||
private void OnRemoved(EntityUid uid, SuppressArtifactContainerComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (!TryComp(args.Entity, out ArtifactComponent? artifact))
|
||||
return;
|
||||
|
||||
artifact.IsSuppressed = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user