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:
Nemanja
2022-11-06 18:05:44 -05:00
committed by GitHub
parent 0d4a605a94
commit 273e0968e4
107 changed files with 3321 additions and 358 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}
}