diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/BiasedArtifactComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/BiasedArtifactComponent.cs
new file mode 100644
index 0000000000..9b42e96ffb
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Components/BiasedArtifactComponent.cs
@@ -0,0 +1,12 @@
+namespace Content.Server.Xenoarchaeology.Equipment.Components;
+
+///
+/// This is used for artifacts that are biased to move
+/// in a particular direction via the
+///
+[RegisterComponent]
+public sealed class BiasedArtifactComponent : Component
+{
+ [ViewVariables]
+ public EntityUid Provider;
+}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs
new file mode 100644
index 0000000000..3530b6af15
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs
@@ -0,0 +1,36 @@
+using Content.Shared.Construction.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.Xenoarchaeology.Equipment.Components;
+
+///
+/// This is used for a machine that biases
+/// an artifact placed on it to move up/down
+///
+[RegisterComponent]
+public sealed class TraversalDistorterComponent : Component
+{
+ [ViewVariables(VVAccess.ReadWrite)]
+ public float BiasChance;
+
+ [DataField("baseBiasChance")]
+ public float BaseBiasChance = 0.7f;
+
+ [DataField("machinePartBiasChance", customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string MachinePartBiasChance = "ScanningModule";
+
+ [DataField("partRatingBiasChance")]
+ public float PartRatingBiasChance = 1.1f;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public BiasDirection BiasDirection = BiasDirection.In;
+
+ public TimeSpan NextActivation = default!;
+ public TimeSpan ActivationDelay = TimeSpan.FromSeconds(1);
+}
+
+public enum BiasDirection : byte
+{
+ In, //down the tree, towards depth 0
+ Out //up the tree, away from depth 0
+}
diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs
new file mode 100644
index 0000000000..f1dacaabca
--- /dev/null
+++ b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs
@@ -0,0 +1,112 @@
+using Content.Server.Construction;
+using Content.Server.Popups;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Xenoarchaeology.Equipment.Components;
+using Content.Server.Xenoarchaeology.XenoArtifacts;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Robust.Shared.Physics.Events;
+using Robust.Shared.Player;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Xenoarchaeology.Equipment.Systems;
+
+public sealed class TraversalDistorterSystem : EntitySystem
+{
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnInit);
+
+ SubscribeLocalEvent(OnInteract);
+ SubscribeLocalEvent(OnExamine);
+ SubscribeLocalEvent(OnRefreshParts);
+ SubscribeLocalEvent(OnUpgradeExamine);
+
+ SubscribeLocalEvent(OnCollide);
+ SubscribeLocalEvent(OnEndCollide);
+ }
+
+ private void OnInit(EntityUid uid, TraversalDistorterComponent component, MapInitEvent args)
+ {
+ component.NextActivation = _timing.CurTime;
+ }
+
+ private void OnInteract(EntityUid uid, TraversalDistorterComponent component, ActivateInWorldEvent args)
+ {
+ if (args.Handled || !this.IsPowered(uid, EntityManager))
+ return;
+ if (_timing.CurTime < component.NextActivation)
+ return;
+ args.Handled = true;
+ component.NextActivation = _timing.CurTime + component.ActivationDelay;
+
+ component.BiasDirection = component.BiasDirection == BiasDirection.In
+ ? BiasDirection.Out
+ : BiasDirection.In;
+
+ var toPopup = string.Empty;
+ switch (component.BiasDirection)
+ {
+ case BiasDirection.In:
+ toPopup = Loc.GetString("traversal-distorter-set-in");
+ break;
+ case BiasDirection.Out:
+ toPopup = Loc.GetString("traversal-distorter-set-out");
+ break;
+ }
+ _popup.PopupEntity(toPopup, uid, Filter.Pvs(uid));
+ }
+
+ private void OnExamine(EntityUid uid, TraversalDistorterComponent component, ExaminedEvent args)
+ {
+ string examine = string.Empty;
+ switch (component.BiasDirection)
+ {
+ case BiasDirection.In:
+ examine = Loc.GetString("traversal-distorter-desc-in");
+ break;
+ case BiasDirection.Out:
+ examine = Loc.GetString("traversal-distorter-desc-out");
+ break;
+ }
+ args.Message.AddMarkup(examine);
+ }
+
+ private void OnRefreshParts(EntityUid uid, TraversalDistorterComponent component, RefreshPartsEvent args)
+ {
+ var biasRating = args.PartRatings[component.MachinePartBiasChance];
+
+ component.BiasChance = component.BaseBiasChance * MathF.Pow(component.PartRatingBiasChance, biasRating - 1);
+ }
+
+ private void OnUpgradeExamine(EntityUid uid, TraversalDistorterComponent component, UpgradeExamineEvent args)
+ {
+ args.AddPercentageUpgrade("traversal-distorter-upgrade-bias", component.BiasChance / component.BaseBiasChance);
+ }
+
+ private void OnCollide(EntityUid uid, TraversalDistorterComponent component, ref StartCollideEvent args)
+ {
+ var otherEnt = args.OtherFixture.Body.Owner;
+
+ if (!HasComp(otherEnt))
+ return;
+
+ var bias = EnsureComp(otherEnt);
+ bias.Provider = uid;
+ }
+
+ private void OnEndCollide(EntityUid uid, TraversalDistorterComponent component, ref EndCollideEvent args)
+ {
+ var otherEnt = args.OtherFixture.Body.Owner;
+
+ if (!HasComp(otherEnt))
+ return;
+
+ if (TryComp(otherEnt, out var bias) && bias.Provider == uid)
+ RemComp(otherEnt, bias);
+ }
+}
diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs
index 7b535e5b30..a84ad96deb 100644
--- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs
+++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs
@@ -1,6 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Cargo.Systems;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Xenoarchaeology.Equipment.Components;
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using JetBrains.Annotations;
using Robust.Shared.Random;
@@ -160,17 +162,50 @@ public sealed partial class ArtifactSystem : EntitySystem
component.CurrentNode.Triggered = true;
if (component.CurrentNode.Edges.Any())
{
- var undiscoveredNodes = component.CurrentNode.Edges.Where(x => !x.Discovered).ToList();
-
- var newNode = _random.Pick(component.CurrentNode.Edges);
- if (undiscoveredNodes.Any() && _random.Prob(0.75f))
- {
- newNode = _random.Pick(undiscoveredNodes);
- }
+ var newNode = GetNewNode(component);
+ if (newNode == null)
+ return;
EnterNode(uid, ref newNode, component);
}
}
+ private ArtifactNode? GetNewNode(ArtifactComponent component)
+ {
+ if (component.CurrentNode == null)
+ return null;
+
+ var allNodes = component.CurrentNode.Edges;
+
+ if (TryComp(component.Owner, out var bias) &&
+ TryComp(bias.Provider, out var trav) &&
+ _random.Prob(trav.BiasChance) &&
+ this.IsPowered(bias.Provider, EntityManager))
+ {
+ switch (trav.BiasDirection)
+ {
+ case BiasDirection.In:
+ var foo = allNodes.Where(x => x.Depth < component.CurrentNode.Depth).ToList();
+ if (foo.Any())
+ allNodes = foo;
+ break;
+ case BiasDirection.Out:
+ var bar = allNodes.Where(x => x.Depth > component.CurrentNode.Depth).ToList();
+ if (bar.Any())
+ allNodes = bar;
+ break;
+ }
+ }
+
+ var undiscoveredNodes = allNodes.Where(x => !x.Discovered).ToList();
+ var newNode = _random.Pick(allNodes);
+ if (undiscoveredNodes.Any() && _random.Prob(0.75f))
+ {
+ newNode = _random.Pick(undiscoveredNodes);
+ }
+
+ return newNode;
+ }
+
///
/// Try and get a data object from a node
///
diff --git a/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl b/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl
new file mode 100644
index 0000000000..af3039e864
--- /dev/null
+++ b/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl
@@ -0,0 +1,7 @@
+traversal-distorter-set-in = Traversal bias set to "in"
+traversal-distorter-set-out = Traversal bias set to "out"
+
+traversal-distorter-desc-in = The affected artifact's traversal now favors moving inwards to the beginning.
+traversal-distorter-desc-out = The affected artifact's traversal now favors moving outwards towards more dangerous nodes.
+
+traversal-distorter-upgrade-bias = Bias effectiveness
diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml
index bd59ea06f7..4da5fbc1b9 100644
--- a/Resources/Prototypes/Catalog/Research/technologies.yml
+++ b/Resources/Prototypes/Catalog/Research/technologies.yml
@@ -446,6 +446,7 @@
- HighPowerMicroLaserStockPart
- NanoManipulatorStockPart
- AdvancedScanningModuleStockPart
+ - TraversalDistorterMachineCircuitboard #sigh
# Bluespace Theory Technology Tree
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
index 0a93fb97ed..7a9a712d96 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
@@ -147,6 +147,23 @@
materialRequirements:
Glass: 5
+- type: entity
+ id: TraversalDistorterMachineCircuitboard
+ parent: BaseMachineCircuitboard
+ name: traversal distorter machine board
+ description: A machine printed circuit board for a traversal distorter
+ components:
+ - type: Sprite
+ state: science
+ - type: MachineBoard
+ prototype: MachineTraversalDistorter
+ requirements:
+ ScanningModule: 1
+ Capacitor: 2
+ materialRequirements:
+ Steel: 5
+ Cable: 1
+
- type: entity
id: ThermomachineFreezerMachineCircuitBoard
parent: BaseMachineCircuitboard
diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml
index f65025c522..fbff9dec4c 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml
@@ -66,3 +66,50 @@
enum.PowerDeviceVisualLayers.Powered:
True: { visible: true }
False: { visible: false }
+
+- type: entity
+ id: MachineTraversalDistorter
+ parent: [ BaseMachinePowered, ConstructibleMachine ]
+ name: traversal distorter
+ description: A machine capable of distorting the traversal of artifact nodes.
+ components:
+ - type: Sprite
+ noRot: true
+ sprite: Structures/Machines/traversal_distorter.rsi
+ drawdepth: FloorObjects
+ layers:
+ - state: icon
+ - state: unshaded
+ shader: unshaded
+ map: ["enum.PowerDeviceVisualLayers.Powered"]
+ - type: Physics
+ bodyType: Static
+ canCollide: true
+ - type: Fixtures
+ fixtures:
+ - shape:
+ !type:PhysShapeAabb
+ bounds: "-0.35,-0.35,0.35,0.35"
+ density: 190
+ mask:
+ - MachineMask
+ layer:
+ - Impassable
+ - MidImpassable
+ - LowImpassable
+ hard: False
+ - type: Transform
+ noRot: false
+ - type: UpgradePowerDraw
+ powerDrawMultiplier: 0.80
+ scaling: Exponential
+ - type: TraversalDistorter
+ - type: Machine
+ board: TraversalDistorterMachineCircuitboard
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.PowerDeviceVisuals.Powered:
+ enum.PowerDeviceVisualLayers.Powered:
+ True: { visible: true }
+ False: { visible: false }
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 387070a95b..286c538c48 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -300,6 +300,7 @@
- SeedExtractorMachineCircuitboard
- AnalysisComputerCircuitboard
- ArtifactAnalyzerMachineCircuitboard
+ - TraversalDistorterMachineCircuitboard
- type: MaterialStorage
whitelist:
tags:
diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml
index f49ca51063..61f82c7993 100644
--- a/Resources/Prototypes/Recipes/Lathes/electronics.yml
+++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml
@@ -186,6 +186,16 @@
Glass: 900
Gold: 100
+- type: latheRecipe
+ id: TraversalDistorterMachineCircuitboard
+ icon: Objects/Misc/module.rsi/science.png
+ result: TraversalDistorterMachineCircuitboard
+ completetime: 4
+ materials:
+ Steel: 100
+ Glass: 900
+ Gold: 100
+
- type: latheRecipe
id: ReagentGrinderMachineCircuitboard
icon: Objects/Misc/module.rsi/id_mod.png
diff --git a/Resources/Textures/Structures/Machines/traversal_distorter.rsi/icon.png b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/icon.png
new file mode 100644
index 0000000000..53f921e615
Binary files /dev/null and b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/icon.png differ
diff --git a/Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json
new file mode 100644
index 0000000000..413b7c38ed
--- /dev/null
+++ b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/meta.json
@@ -0,0 +1,17 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Created by EmoGarbage404",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "unshaded"
+ }
+ ]
+}
diff --git a/Resources/Textures/Structures/Machines/traversal_distorter.rsi/unshaded.png b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/unshaded.png
new file mode 100644
index 0000000000..0d57a2d8c4
Binary files /dev/null and b/Resources/Textures/Structures/Machines/traversal_distorter.rsi/unshaded.png differ