[Sci] Non-destructive XenoArch Research (#15398)

* Non-destructive XenoArch research

* nerf the price

* Points -> Extract
This commit is contained in:
Nemanja
2023-04-17 01:57:21 -04:00
committed by GitHub
parent adb6b168b7
commit 2a83a9bc17
11 changed files with 89 additions and 167 deletions

View File

@@ -23,19 +23,19 @@ public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
_consoleMenu.OnClose += Close;
_consoleMenu.OpenCentered();
_consoleMenu.OnServerSelectionButtonPressed += _ =>
_consoleMenu.OnServerSelectionButtonPressed += () =>
{
SendMessage(new AnalysisConsoleServerSelectionMessage());
};
_consoleMenu.OnScanButtonPressed += _ =>
_consoleMenu.OnScanButtonPressed += () =>
{
SendMessage(new AnalysisConsoleScanButtonPressedMessage());
};
_consoleMenu.OnPrintButtonPressed += _ =>
_consoleMenu.OnPrintButtonPressed += () =>
{
SendMessage(new AnalysisConsolePrintButtonPressedMessage());
};
_consoleMenu.OnDestroyButtonPressed += _ =>
_consoleMenu.OnDestroyButtonPressed += () =>
{
SendMessage(new AnalysisConsoleDestroyButtonPressedMessage());
};
@@ -61,7 +61,6 @@ public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
if (!disposing)
return;
_consoleMenu?.AnalysisDestroyWindow?.Close();
_consoleMenu?.Dispose();
}
}

View File

@@ -3,7 +3,6 @@ using Content.Client.UserInterface.Controls;
using Content.Shared.Xenoarchaeology.Equipment;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
@@ -13,42 +12,20 @@ namespace Content.Client.Xenoarchaeology.Ui;
public sealed partial class AnalysisConsoleMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _ent = default!;
public AnalysisDestroyWindow? AnalysisDestroyWindow;
public event Action<BaseButton.ButtonEventArgs>? OnServerSelectionButtonPressed;
public event Action<BaseButton.ButtonEventArgs>? OnScanButtonPressed;
public event Action<BaseButton.ButtonEventArgs>? OnPrintButtonPressed;
public event Action<BaseButton.ButtonEventArgs>? OnDestroyButtonPressed;
public event Action? OnServerSelectionButtonPressed;
public event Action? OnScanButtonPressed;
public event Action? OnPrintButtonPressed;
public event Action? OnDestroyButtonPressed;
public AnalysisConsoleMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
ServerSelectionButton.OnPressed += a => OnServerSelectionButtonPressed?.Invoke(a);
ScanButton.OnPressed += a => OnScanButtonPressed?.Invoke(a);
PrintButton.OnPressed += a => OnPrintButtonPressed?.Invoke(a);
DestroyButton.OnPressed += _ => OnDestroyButton();
}
private void OnDestroyButton()
{
// check if window is already open
if (AnalysisDestroyWindow is { IsOpen: true })
{
AnalysisDestroyWindow.MoveToFront();
return;
}
// open a new one
AnalysisDestroyWindow = new ();
AnalysisDestroyWindow.OpenCentered();
AnalysisDestroyWindow.OnYesButton += a =>
{
OnDestroyButtonPressed?.Invoke(a);
};
ServerSelectionButton.OnPressed += _ => OnServerSelectionButtonPressed?.Invoke();
ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke();
PrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke();
DestroyButton.OnPressed += _ => OnDestroyButtonPressed?.Invoke();
}
public void SetButtonsDisabled(AnalysisConsoleScanUpdateState state)
@@ -56,7 +33,7 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
ScanButton.Disabled = !state.CanScan;
PrintButton.Disabled = !state.CanPrint;
var disabled = !state.ServerConnected || !state.CanScan;
var disabled = !state.ServerConnected || !state.CanScan || state.PointAmount <= 0;
DestroyButton.Disabled = disabled;
@@ -128,12 +105,5 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
("seconds", (int) state.TotalTime.TotalSeconds - (int) state.TimeRemaining.TotalSeconds));
ProgressBar.Value = (float) state.TimeRemaining.Divide(state.TotalTime);
}
public override void Close()
{
base.Close();
AnalysisDestroyWindow?.Close();
}
}

View File

@@ -1,22 +0,0 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'analysis-destroy-window-title'}"
MinSize="256 100">
<BoxContainer
Margin="10 10"
HorizontalExpand="True"
Orientation="Vertical"
VerticalExpand="True">
<Label Text="{Loc 'analysis-destroy-window-text'}" />
<BoxContainer
Margin="10 10 10 10"
VerticalAlignment="Bottom"
Orientation="Horizontal"
HorizontalExpand="True">
<Button Name="YesButton" Text="{Loc 'analysis-destroy-window-yes'}" HorizontalExpand="True"></Button>
<BoxContainer SetSize="10 10"></BoxContainer>
<Button Name="NoButton" Text="{Loc 'analysis-destroy-window-no'}" HorizontalExpand="True"></Button>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -1,26 +0,0 @@
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Xenoarchaeology.Ui;
[GenerateTypedNameReferences]
public sealed partial class AnalysisDestroyWindow : FancyWindow
{
public event Action<BaseButton.ButtonEventArgs>? OnYesButton;
public AnalysisDestroyWindow()
{
RobustXamlLoader.Load(this);
YesButton.AddStyleClass(StyleBase.ButtonCaution);
YesButton.OnPressed += a =>
{
OnYesButton?.Invoke(a);
Close();
};
NoButton.OnPressed += _ => Close();
}
}

View File

@@ -35,7 +35,7 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambienntSound = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly ArtifactSystem _artifact = default!;
@@ -199,6 +199,7 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
var totalTime = TimeSpan.Zero;
var canScan = false;
var canPrint = false;
var points = 0;
if (component.AnalyzerEntity != null && TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity, out var analyzer))
{
artifact = analyzer.LastAnalyzedArtifact;
@@ -206,6 +207,10 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
totalTime = analyzer.AnalysisDuration * analyzer.AnalysisDurationMulitplier;
canScan = analyzer.Contacts.Any();
canPrint = analyzer.ReadyToPrint;
// the artifact that's actually on the scanner right now.
if (GetArtifactForAnalysis(component.AnalyzerEntity, analyzer) is { } current)
points = _artifact.GetResearchPointValue(current);
}
var analyzerConnected = component.AnalyzerEntity != null;
var serverConnected = TryComp<ResearchClientComponent>(uid, out var client) && client.ConnectedToServer;
@@ -214,7 +219,7 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
var remaining = active != null ? _timing.CurTime - active.StartTime : TimeSpan.Zero;
var state = new AnalysisConsoleScanUpdateState(artifact, analyzerConnected, serverConnected,
canScan, canPrint, msg, scanning, remaining, totalTime);
canScan, canPrint, msg, scanning, remaining, totalTime, points);
var bui = _ui.GetUi(uid, ArtifactAnalzyerUiKey.Key);
_ui.SetUiState(bui, state);
@@ -347,22 +352,21 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
if (!_research.TryGetClientServer(uid, out var server, out var serverComponent))
return;
var entToDestroy = GetArtifactForAnalysis(component.AnalyzerEntity);
if (entToDestroy == null)
var artifact = GetArtifactForAnalysis(component.AnalyzerEntity);
if (artifact == null)
return;
if (TryComp<ArtifactAnalyzerComponent>(component.AnalyzerEntity.Value, out var analyzer) &&
analyzer.LastAnalyzedArtifact == entToDestroy)
{
ResetAnalyzer(component.AnalyzerEntity.Value);
}
var pointValue = _artifact.GetResearchPointValue(artifact.Value);
_research.AddPointsToServer(server.Value, _artifact.GetResearchPointValue(entToDestroy.Value), serverComponent);
EntityManager.DeleteEntity(entToDestroy.Value);
if (pointValue == 0)
return;
_research.AddPointsToServer(server.Value, pointValue, serverComponent);
_artifact.AdjustConsumedPoints(artifact.Value, pointValue);
_audio.PlayPvs(component.DestroySound, component.AnalyzerEntity.Value, AudioParams.Default.WithVolume(2f));
_popup.PopupEntity(Loc.GetString("analyzer-artifact-destroy-popup"),
_popup.PopupEntity(Loc.GetString("analyzer-artifact-extract-popup"),
component.AnalyzerEntity.Value, PopupType.Large);
UpdateUserInterface(uid, component);
@@ -371,9 +375,6 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
/// <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);
@@ -382,9 +383,6 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
/// <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))
@@ -399,9 +397,6 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
/// <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)
{
@@ -423,9 +418,6 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
/// <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)
{
@@ -485,7 +477,7 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
if (TryComp<ApcPowerReceiverComponent>(uid, out var powa))
powa.NeedsPower = true;
_ambienntSound.SetAmbience(uid, true);
_ambientSound.SetAmbience(uid, true);
}
private void OnAnalyzeEnd(EntityUid uid, ActiveArtifactAnalyzerComponent component, ComponentShutdown args)
@@ -493,7 +485,7 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem
if (TryComp<ApcPowerReceiverComponent>(uid, out var powa))
powa.NeedsPower = false;
_ambienntSound.SetAmbience(uid, false);
_ambientSound.SetAmbience(uid, false);
}
private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent component, ref PowerChangedEvent args)

View File

@@ -6,6 +6,8 @@ namespace Content.Server.Xenoarchaeology.Equipment.Systems;
public sealed class SuppressArtifactContainerSystem : EntitySystem
{
[Dependency] private readonly ArtifactSystem _artifact = default!;
public override void Initialize()
{
base.Initialize();
@@ -15,17 +17,17 @@ public sealed class SuppressArtifactContainerSystem : EntitySystem
private void OnInserted(EntityUid uid, SuppressArtifactContainerComponent component, EntInsertedIntoContainerMessage args)
{
if (!TryComp(args.Entity, out ArtifactComponent? artifact))
if (!TryComp<ArtifactComponent>(args.Entity, out var artifact))
return;
artifact.IsSuppressed = true;
_artifact.SetIsSuppressed(args.Entity, true, artifact);
}
private void OnRemoved(EntityUid uid, SuppressArtifactContainerComponent component, EntRemovedFromContainerMessage args)
{
if (!TryComp(args.Entity, out ArtifactComponent? artifact))
if (!TryComp<ArtifactComponent>(args.Entity, out var artifact))
return;
artifact.IsSuppressed = false;
_artifact.SetIsSuppressed(args.Entity, false, artifact);
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Server.Xenoarchaeology.XenoArtifacts;
[RegisterComponent]
[RegisterComponent, Access(typeof(ArtifactSystem))]
public sealed class ArtifactComponent : Component
{
/// <summary>
@@ -53,22 +53,29 @@ public sealed class ArtifactComponent : Component
public TimeSpan LastActivationTime;
/// <summary>
/// The base price of each node for an artifact
/// A multiplier applied to the calculated point value
/// to determine the monetary value of the artifact
/// </summary>
[DataField("pricePerNode")]
public int PricePerNode = 500;
[DataField("priceMultiplier"), ViewVariables(VVAccess.ReadWrite)]
public float PriceMultiplier = 0.05f;
/// <summary>
/// The base amount of research points for each artifact node.
/// </summary>
[DataField("pointsPerNode")]
[DataField("pointsPerNode"), ViewVariables(VVAccess.ReadWrite)]
public int PointsPerNode = 5000;
/// <summary>
/// Research points which have been "consumed" from the theoretical max value of the artifact.
/// </summary>
[DataField("consumedPoints"), ViewVariables(VVAccess.ReadWrite)]
public int ConsumedPoints;
/// <summary>
/// A multiplier that is raised to the power of the average depth of a node.
/// Used for calculating the research point value of an artifact node.
/// </summary>
[DataField("pointDangerMultiplier")]
[DataField("pointDangerMultiplier"), ViewVariables(VVAccess.ReadWrite)]
public float PointDangerMultiplier = 1.35f;
}

View File

@@ -51,29 +51,7 @@ public sealed partial class ArtifactSystem : EntitySystem
/// </remarks>
private void GetPrice(EntityUid uid, ArtifactComponent component, ref PriceCalculationEvent args)
{
var price = component.NodeTree.Sum(x => GetNodePrice(x, component));
// 25% bonus for fully exploring every node.
var fullyExploredBonus = component.NodeTree.Any(x => !x.Triggered) ? 1 : 1.25f;
args.Price =+ price * fullyExploredBonus;
}
private float GetNodePrice(ArtifactNode node, ArtifactComponent component)
{
if (!node.Discovered) //no money for undiscovered nodes.
return 0;
var triggerProto = _prototype.Index<ArtifactTriggerPrototype>(node.Trigger);
var effectProto = _prototype.Index<ArtifactEffectPrototype>(node.Effect);
//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 + effectProto.TargetDepth + triggerProto.TargetDepth) / 3;
var price = MathF.Pow(2f, nodeDanger) * component.PricePerNode * priceMultiplier;
return price;
args.Price =+ GetResearchPointValue(uid, component) * component.PriceMultiplier;
}
/// <summary>
@@ -96,9 +74,32 @@ public sealed partial class ArtifactSystem : EntitySystem
var sumValue = component.NodeTree.Sum(n => GetNodePointValue(n, component, getMaxPrice));
var fullyExploredBonus = component.NodeTree.All(x => x.Triggered) || getMaxPrice ? 1.25f : 1;
sumValue -= component.ConsumedPoints;
var pointValue = (int) (sumValue * fullyExploredBonus);
return pointValue;
return (int) (sumValue * fullyExploredBonus);
}
/// <summary>
/// Adjusts how many points on the artifact have been consumed
/// </summary>
public void AdjustConsumedPoints(EntityUid uid, int amount, ArtifactComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.ConsumedPoints += amount;
}
/// <summary>
/// Sets whether or not the artifact is suppressed,
/// preventing it from activating
/// </summary>
public void SetIsSuppressed(EntityUid uid, bool suppressed, ArtifactComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.IsSuppressed = suppressed;
}
/// <summary>
@@ -201,8 +202,8 @@ public sealed partial class ArtifactSystem : EntitySystem
var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component);
var allNodes = currentNode.Edges;
_sawmill.Debug("artifact", $"our node: {currentNode.Id}");
_sawmill.Debug("artifact", $"other nodes: {string.Join(", ", allNodes)}");
_sawmill.Debug($"our node: {currentNode.Id}");
_sawmill.Debug($"other nodes: {string.Join(", ", allNodes)}");
if (TryComp<BiasedArtifactComponent>(uid, out var bias) &&
TryComp<TraversalDistorterComponent>(bias.Provider, out var trav) &&
@@ -225,14 +226,14 @@ public sealed partial class ArtifactSystem : EntitySystem
}
var undiscoveredNodes = allNodes.Where(x => !GetNodeFromId(x, component).Discovered).ToList();
_sawmill.Debug("artifact", $"Undiscovered nodes: {string.Join(", ", undiscoveredNodes)}");
_sawmill.Debug($"Undiscovered nodes: {string.Join(", ", undiscoveredNodes)}");
var newNode = _random.Pick(allNodes);
if (undiscoveredNodes.Any() && _random.Prob(0.75f))
{
newNode = _random.Pick(undiscoveredNodes);
}
_sawmill.Debug("artifact", $"Going to node {newNode}");
_sawmill.Debug($"Going to node {newNode}");
return GetNodeFromId(newNode, component);
}

View File

@@ -50,8 +50,10 @@ public sealed class AnalysisConsoleScanUpdateState : BoundUserInterfaceState
public TimeSpan TotalTime;
public int PointAmount;
public AnalysisConsoleScanUpdateState(EntityUid? artifact, bool analyzerConnected, bool serverConnected, bool canScan, bool canPrint,
FormattedMessage? scanReport, bool scanning, TimeSpan timeRemaining, TimeSpan totalTime)
FormattedMessage? scanReport, bool scanning, TimeSpan timeRemaining, TimeSpan totalTime, int pointAmount)
{
Artifact = artifact;
AnalyzerConnected = analyzerConnected;
@@ -64,5 +66,7 @@ public sealed class AnalysisConsoleScanUpdateState : BoundUserInterfaceState
Scanning = scanning;
TimeRemaining = timeRemaining;
TotalTime = totalTime;
PointAmount = pointAmount;
}
}

View File

@@ -4,8 +4,8 @@ analysis-console-scan-button = Scan
analysis-console-scan-tooltip-info = Scan artifacts to learn information about their structure.
analysis-console-print-button = Print
analysis-console-print-tooltip-info = Print out the current information about the artifact.
analysis-console-destroy-button = Destroy
analysis-console-destroy-button-info = Destroy artifacts to generate points based on how much has been unlocked.
analysis-console-destroy-button = Extract
analysis-console-destroy-button-info = Extract points from an artifact based on the explored nodes.
analysis-console-info-no-scanner = No analyzer connected! Please connect one using a multitool.
analysis-console-info-no-artifact = No artifact present! Place one on the pad then scan for information.
@@ -26,14 +26,9 @@ analysis-console-progress-text = {$seconds ->
*[other] T-{$seconds} seconds
}
analysis-destroy-window-title = Confirm Destruction
analysis-destroy-window-text = Destroy the artifact, converting it into research points?
analysis-destroy-window-yes = Yes
analysis-destroy-window-no = No
analyzer-artifact-component-upgrade-analysis = analysis duration
analysis-console-print-popup = The console printed out a report.
analyzer-artifact-destroy-popup = The artifact disintegrated into energy!
analyzer-artifact-extract-popup = Energy shimmers on the artifact's surface!
analysis-report-title = Artifact Report: Node {$id}

View File

@@ -24,6 +24,6 @@ The main equipment that you'll be using for Xenoarchaeology is the [color=#a4885
To set them up, simply link them with a multitool, set an artifact on top of the analyzer, and press the [color=#a4885c]Scan[/color] button.
Using the console, you can permanently destroy an artifact in exchange for points. This is irreversible, so be sure to confirm with your department that all research on it has concluded.
Using the console, you can extract points from the artifact using the [color=#a4885c]Extract[/color] button. The amount of points you extract is based on how many of the nodes of the artifact have been activated.
</Document>