diff --git a/Content.Client/_White/DNAConsole/DNAConsoleBoundInterface.cs b/Content.Client/_White/DNAConsole/DNAConsoleBoundInterface.cs
new file mode 100644
index 00000000000..06fd3f27859
--- /dev/null
+++ b/Content.Client/_White/DNAConsole/DNAConsoleBoundInterface.cs
@@ -0,0 +1,49 @@
+using Content.Shared.DNAConsole;
+using JetBrains.Annotations;
+
+namespace Content.Client.DNAConsole.UI
+{
+ [UsedImplicitly]
+ public sealed class DNAConsoleBoundUserInterface : BoundUserInterface
+ {
+ [ViewVariables]
+ private DNAConsoleWindow? _window;
+
+ public DNAConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _window = new DNAConsoleWindow
+ {
+ Title = "DNAConsole"
+ };
+ _window.OnClose += Close;
+ // _window.ModifierButton.OnPressed += _ => SendMessage(new UiButtonPressedMessage(UiButton.Clone));
+ _window.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ _window?.Populate((DNAConsoleBoundUserInterfaceState) state);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ if (_window != null)
+ {
+ _window.OnClose -= Close;
+ //_window.DNAButton.OnPressed -= _ => SendMessage(new UiButtonPressedMessage(UiButton.Clone));
+ }
+ _window?.Dispose();
+ }
+ }
+}
diff --git a/Content.Client/_White/DNAConsole/DNAConsoleWindow.xaml b/Content.Client/_White/DNAConsole/DNAConsoleWindow.xaml
new file mode 100644
index 00000000000..21f7722829a
--- /dev/null
+++ b/Content.Client/_White/DNAConsole/DNAConsoleWindow.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_White/DNAConsole/DNAConsoleWindow.xaml.cs b/Content.Client/_White/DNAConsole/DNAConsoleWindow.xaml.cs
new file mode 100644
index 00000000000..b5e607b1fbc
--- /dev/null
+++ b/Content.Client/_White/DNAConsole/DNAConsoleWindow.xaml.cs
@@ -0,0 +1,86 @@
+using Content.Client.Message;
+using Content.Shared.DNAConsole;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DNAConsole;
+
+[GenerateTypedNameReferences]
+public sealed partial class DNAConsoleWindow : DefaultWindow
+{
+
+public DNAConsoleWindow()
+{
+ RobustXamlLoader.Load(this);
+}
+
+private DNAConsoleBoundUserInterfaceState? _lastUpdate;
+
+public void Populate(DNAConsoleBoundUserInterfaceState state)
+ {
+ _lastUpdate = state;
+ // BUILD SCANNER UI
+ if (state.ModifierConnected)
+ {
+ if (!state.ModifierInRange)
+ {
+ DNAModifierFar.Visible = true;
+ DNAConsoleContent.Visible = false;
+ DNAModifierMissing.Visible = false;
+ return;
+ }
+
+ DNAConsoleContent.Visible = true;
+ DNAModifierFar.Visible = false;
+ DNAModifierMissing.Visible = false;
+
+ switch (state.ModifierStatus)
+ {
+ case ModifierStatus.Ready:
+ ModifierActivity.Text = (Loc.GetString("dna-console-component-msg-ready"));
+ break;
+ case ModifierStatus.ModifierOccupied:
+ ModifierActivity.Text = (Loc.GetString("dna-console-component-msg-occupied"));
+ break;
+ case ModifierStatus.ModifierEmpty:
+ ModifierActivity.Text = (Loc.GetString("dna-console-component-msg-empty"));
+ break;
+ case ModifierStatus.OccupantMetaphyiscal:
+ ModifierActivity.Text = (Loc.GetString("dna-console-component-msg-already-alive"));
+ break;
+ }
+ // Set label depending on if scanner is occupied or not.
+ ModifierInfoLabel.SetMarkup(state.ModifierBodyInfo != null ?
+ Loc.GetString("dna-console-window-scanner-id", ("modifierOccupantName", state.ModifierBodyInfo)) :
+ Loc.GetString("dna-console-window-id-blank"));
+ }
+ else
+ {
+ // Scanner is missing, set error message visible
+ DNAConsoleContent.Visible = false;
+ DNAModifierFar.Visible = false;
+ DNAModifierMissing.Visible = true;
+ }
+
+ // BUILD ClONER UI
+ if (state.ModifierConnected)
+ {
+ if (!state.ModifierInRange)
+ {
+ DNATest.Visible = false;
+ return;
+ }
+
+ DNATest.Visible = true;
+ }
+ else
+ {
+ // Clone pod is missing, set error message visible
+ DNATest.Visible = false;
+ }
+ }
+
+}
+
diff --git a/Content.Client/_White/DNAModifier/DNAModifierComponent.cs b/Content.Client/_White/DNAModifier/DNAModifierComponent.cs
new file mode 100644
index 00000000000..60fb6ace3de
--- /dev/null
+++ b/Content.Client/_White/DNAModifier/DNAModifierComponent.cs
@@ -0,0 +1,8 @@
+using Content.Shared.DNAModifier;
+
+namespace Content.Client.DNAModifier;
+
+[RegisterComponent]
+public sealed partial class DNAModifierComponent : SharedDNAModifierComponent
+{
+}
diff --git a/Content.Client/_White/Genetics/DNAScannerBoundUserInterface.cs b/Content.Client/_White/Genetics/DNAScannerBoundUserInterface.cs
new file mode 100644
index 00000000000..d9399c56cb0
--- /dev/null
+++ b/Content.Client/_White/Genetics/DNAScannerBoundUserInterface.cs
@@ -0,0 +1,26 @@
+namespace Content.Client._White.Genetics
+{
+ [UserImplicitly]
+ public sealed class DNAScannerBoundUserInterface : BoundUserInterface
+ {
+ [ViewVariables]
+ private DNAScannerWindow? _window;
+
+ public DNAScannerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _window = new DNAScannerWindow
+ {
+ Title = EntMan.GetComponent(Owner).EntityName,
+ };
+ _window.OnClose += Close;
+ _window.OpenCentered();
+ }
+
+ }
+
+}
diff --git a/Content.Client/_White/Genetics/DNAScannerWindow.xaml b/Content.Client/_White/Genetics/DNAScannerWindow.xaml
new file mode 100644
index 00000000000..fb78443dc19
--- /dev/null
+++ b/Content.Client/_White/Genetics/DNAScannerWindow.xaml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/Content.Client/_White/Genetics/DNAScannerWindow.xaml.cs b/Content.Client/_White/Genetics/DNAScannerWindow.xaml.cs
new file mode 100644
index 00000000000..f7a1f4a4afe
--- /dev/null
+++ b/Content.Client/_White/Genetics/DNAScannerWindow.xaml.cs
@@ -0,0 +1,15 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._White.Genetics;
+
+[GenerateTypedNameReferences]
+public sealed partial class DNAScannerWindow : Control
+{
+ public DNAScannerWindow()
+ {
+ RobustXamlLoader.Load(this);
+ }
+}
+
diff --git a/Content.Server/_White/Genetics/Components/ActiveModifierComponent.cs b/Content.Server/_White/Genetics/Components/ActiveModifierComponent.cs
new file mode 100644
index 00000000000..ad723d950e7
--- /dev/null
+++ b/Content.Server/_White/Genetics/Components/ActiveModifierComponent.cs
@@ -0,0 +1,9 @@
+namespace Content.Server._White.Genetics.Components;
+
+///
+/// Shrimply a tracking component for pods that are cloning.
+///
+[RegisterComponent]
+public sealed partial class ActiveModifierComponent : Component
+{
+}
diff --git a/Content.Server/_White/Genetics/Components/DNAConsoleComponent.cs b/Content.Server/_White/Genetics/Components/DNAConsoleComponent.cs
new file mode 100644
index 00000000000..a5ae999a5d7
--- /dev/null
+++ b/Content.Server/_White/Genetics/Components/DNAConsoleComponent.cs
@@ -0,0 +1,17 @@
+namespace Content.Server._White.Genetics.Components
+{
+ [RegisterComponent]
+ public sealed partial class DNAConsoleComponent : Component
+ {
+ public const string ScannerPort = "DNAModifierSender";
+
+ [ViewVariables]
+ public EntityUid? Modifier = null;
+
+ /// Maximum distance between console and one if its machines
+ [DataField("maxDistance")]
+ public float MaxDistance = 4f;
+
+ public bool ModifierInRange = true;
+ }
+}
diff --git a/Content.Server/_White/Genetics/Components/DNAModifierComponent.cs b/Content.Server/_White/Genetics/Components/DNAModifierComponent.cs
new file mode 100644
index 00000000000..c75bc49a464
--- /dev/null
+++ b/Content.Server/_White/Genetics/Components/DNAModifierComponent.cs
@@ -0,0 +1,16 @@
+using Content.Shared.DNAModifier;
+using Robust.Shared.Containers;
+
+namespace Content.Server._White.Genetics.Components
+{
+ [RegisterComponent]
+ public sealed partial class DNAModifierComponent : SharedDNAModifierComponent
+ {
+ public const string ScannerPort = "DNAModifierReceiver";
+ public ContainerSlot BodyContainer = default!;
+ public EntityUid? ConnectedConsole;
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float CloningFailChanceMultiplier = 1f;
+ }
+}
diff --git a/Content.Server/_White/Genetics/Components/DNAScannerComponent.cs b/Content.Server/_White/Genetics/Components/DNAScannerComponent.cs
new file mode 100644
index 00000000000..19e816fb166
--- /dev/null
+++ b/Content.Server/_White/Genetics/Components/DNAScannerComponent.cs
@@ -0,0 +1,39 @@
+using Robust.Shared.Audio;
+
+namespace Content.Server._White.Genetics.Components;
+
+///
+///
+///
+public sealed partial class DNAScannerComponent : Component
+{
+ ///
+ /// How long it takes to scan someone.
+ ///
+ [DataField("UseDelay")]
+ public TimeSpan ScanDelay = TimeSpan.FromSeconds(0.8);
+
+ ///
+ /// The maximum range in tiles at which the analyzer can receive continuous updates
+ ///
+ [DataField]
+ public float MaxScanRange = 2.5f;
+
+ ///
+ /// Sound played on scanning begin
+ ///
+ [DataField]
+ public SoundSpecifier? ScanningBeginSound;
+
+ ///
+ /// Sound played on scanning end
+ ///
+ [DataField]
+ public SoundSpecifier? ScanningEndSound;
+
+ ///
+ /// Genome that was scanned.
+ ///
+ [DataField]
+ public GenomeComponent? ScannedGenome;
+}
diff --git a/Content.Server/_White/Genetics/Components/GenomeComponent.cs b/Content.Server/_White/Genetics/Components/GenomeComponent.cs
new file mode 100644
index 00000000000..ada893dd21e
--- /dev/null
+++ b/Content.Server/_White/Genetics/Components/GenomeComponent.cs
@@ -0,0 +1,66 @@
+using Content.Shared._White.Genetics;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server._White.Genetics.Components;
+
+///
+/// Gives this entity a genome for traits to be passed on and potentially mutated.
+/// Both of those must be handled by other systems, on its own it has no functionality.
+/// TODO: комплиментарные последовательности
+///
+[RegisterComponent]
+public sealed partial class GenomeComponent : Component
+{
+ ///
+ /// Name of the to create on init.
+ ///
+ [DataField(required: true)]
+ public ProtoId GenomeId = string.Empty;
+
+ ///
+ /// Genome layout to use for this round and genome type.
+ /// Stored in .
+ ///
+ [ViewVariables]
+ public GenomeLayout Layout = new();
+
+ ///
+ /// Activated mutations. Acquired through activators or radiation.
+ ///
+ [ViewVariables]
+ public List ActivatedMutations = new List();
+
+ ///
+ /// Mutations that were acquired via mutators.
+ ///
+ [DataField]
+ public List MutatedMutations = new List();
+
+ ///
+ /// TODO: wtf is this
+ ///
+ [DataField("humanGenes")]
+ public bool HumanGenes = false;
+
+ ///
+ /// Key is the name of the region in GenomeLayout, value is the name of corresponding mutation.
+ ///
+ [DataField]
+ public Dictionary MutationRegions = new Dictionary();
+
+ ///
+ /// Genome bits themselves.
+ /// Data can be retrieved with comp.Layout.GetInt(comp.Genome, "name"), etc.
+ ///
+ ///
+ /// Completely empty by default, another system must use to load genes or copy from a parent.
+ ///
+ [DataField]
+ public Genome Genome = new();
+
+ ///
+ /// It is changed when a mutation through mutator is applied.
+ ///
+ [DataField]
+ public int Instability = 0;
+}
diff --git a/Content.Server/_White/Genetics/DNAConsoleSystem.cs b/Content.Server/_White/Genetics/DNAConsoleSystem.cs
new file mode 100644
index 00000000000..bcec1812238
--- /dev/null
+++ b/Content.Server/_White/Genetics/DNAConsoleSystem.cs
@@ -0,0 +1,200 @@
+using System.Linq;
+using Content.Server._White.Genetics.Components;
+using Content.Server.Administration.Logs;
+using Content.Server.DeviceLinking.Systems;
+using Content.Server.Power.Components;
+using Content.Server.Power.EntitySystems;
+using Content.Shared.DeviceLinking;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.DNAConsole;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Mind;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.UserInterface;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+
+namespace Content.Server._White.Genetics
+{
+ [UsedImplicitly]
+ public sealed class CloningConsoleSystem : EntitySystem
+ {
+ [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
+ [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+ [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
+ [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
+ [Dependency] private readonly SharedMindSystem _mindSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnButtonPressed);
+ SubscribeLocalEvent(OnUIOpen);
+ SubscribeLocalEvent(OnPowerChanged);
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnNewLink);
+ SubscribeLocalEvent(OnPortDisconnected);
+ SubscribeLocalEvent(OnAnchorChanged);
+ }
+
+ private void OnInit(EntityUid uid, DNAConsoleComponent component, ComponentInit args)
+ {
+ _signalSystem.EnsureSourcePorts(uid, DNAConsoleComponent.ScannerPort);
+ }
+ private void OnButtonPressed(EntityUid uid, DNAConsoleComponent consoleComponent, UiButtonPressedMessage args)
+ {
+ if (!_powerReceiverSystem.IsPowered(uid))
+ return;
+/*
+ПОСЛЕ ГЕНОВ
+ switch (args.Button)
+ {
+ case UiButton.Clone:
+ if (consoleComponent.Modifier != null)
+ TryClone(uid,consoleComponent.Modifier.Value, consoleComponent: consoleComponent);
+ break;
+ }
+*/
+ UpdateUserInterface(uid, consoleComponent);
+ }
+
+ private void OnPowerChanged(EntityUid uid, DNAConsoleComponent component, ref PowerChangedEvent args)
+ {
+ UpdateUserInterface(uid, component);
+ }
+
+ private void OnMapInit(EntityUid uid, DNAConsoleComponent component, MapInitEvent args)
+ {
+ if (!TryComp(uid, out var receiver))
+ return;
+
+ foreach (var port in receiver.Outputs.Values.SelectMany(ports => ports))
+ {
+ if (TryComp(port, out var scanner))
+ {
+ component.Modifier = port;
+ scanner.ConnectedConsole = uid;
+ }
+ }
+ }
+
+ private void OnNewLink(EntityUid uid, DNAConsoleComponent component, NewLinkEvent args)
+ {
+ if (TryComp(args.Sink, out var scanner) && args.SourcePort == DNAConsoleComponent.ScannerPort)
+ {
+ component.Modifier = args.Sink;
+ scanner.ConnectedConsole = uid;
+ }
+
+ RecheckConnections(uid, component.Modifier, component);
+ }
+
+ private void OnPortDisconnected(EntityUid uid, DNAConsoleComponent component, PortDisconnectedEvent args)
+ {
+ if (args.Port == DNAConsoleComponent.ScannerPort)
+ component.Modifier = null;
+
+ UpdateUserInterface(uid, component);
+ }
+
+ private void OnUIOpen(EntityUid uid, DNAConsoleComponent component, AfterActivatableUIOpenEvent args)
+ {
+ UpdateUserInterface(uid, component);
+ }
+
+ private void OnAnchorChanged(EntityUid uid, DNAConsoleComponent component, ref AnchorStateChangedEvent args)
+ {
+ if (args.Anchored)
+ {
+ RecheckConnections(uid, component.Modifier, component);
+ return;
+ }
+ UpdateUserInterface(uid, component);
+ }
+
+ public void UpdateUserInterface(EntityUid consoleUid, DNAConsoleComponent consoleComponent)
+ {
+ if (!_uiSystem.TryGetUi(consoleUid, DNAConsoleUiKey.Key, out var ui))
+ return;
+
+ if (!_powerReceiverSystem.IsPowered(consoleUid))
+ {
+ _uiSystem.CloseAll(ui);
+ return;
+ }
+
+ var newState = GetUserInterfaceState(consoleComponent);
+ _uiSystem.SetUiState(ui, newState);
+ }
+/*
+ public void TryGens(EntityUid uid, DNAModifierComponent? scannerComp = null, DNAConsoleComponent? consoleComponent = null)
+ {
+ логика на гены, будет после добавления самих генов
+ }
+
+*/
+
+ public void RecheckConnections(EntityUid console, EntityUid? scanner, DNAConsoleComponent? consoleComp = null)
+ {
+ if (!Resolve(console, ref consoleComp))
+ return;
+
+ if (scanner != null)
+ {
+ Transform(scanner.Value).Coordinates.TryDistance(EntityManager, Transform((console)).Coordinates, out float scannerDistance);
+ consoleComp.ModifierInRange = scannerDistance <= consoleComp.MaxDistance;
+ }
+
+ UpdateUserInterface(console, consoleComp);
+ }
+ private DNAConsoleBoundUserInterfaceState GetUserInterfaceState(DNAConsoleComponent consoleComponent)
+ {
+ ModifierStatus _modifierStatus = ModifierStatus.Ready;
+
+ // modifier info
+ string scanBodyInfo = Loc.GetString("generic-unknown");
+ bool modifierConnected = false;
+ bool modifierInRange = consoleComponent.ModifierInRange;
+ if (consoleComponent.Modifier != null && TryComp(consoleComponent.Modifier, out var scanner))
+ {
+ modifierConnected = true;
+ EntityUid? scanBody = scanner.BodyContainer.ContainedEntity;
+
+ // GET STATE
+ if (scanBody == null || !HasComp(scanBody))
+ _modifierStatus = ModifierStatus.ModifierEmpty;
+ else
+ {
+ scanBodyInfo = MetaData(scanBody.Value).EntityName;
+
+ if (!_mobStateSystem.IsDead(scanBody.Value))
+ {
+ _modifierStatus = ModifierStatus.ModifierOccupantAlive;
+ }
+ }
+
+ var modifierBodyInfo = Loc.GetString("generic-unknown");
+
+ if (HasComp(consoleComponent.Modifier))
+ {
+ if(scanBody != null)
+ modifierBodyInfo = Identity.Name(scanBody.Value, EntityManager);
+ _modifierStatus = ModifierStatus.ModifierOccupied;
+ }
+ }
+
+ return new DNAConsoleBoundUserInterfaceState(
+ scanBodyInfo,
+ _modifierStatus,
+ modifierConnected,
+ modifierInRange
+ );
+ }
+
+ }
+}
diff --git a/Content.Server/_White/Genetics/DNAModifierSystem.cs b/Content.Server/_White/Genetics/DNAModifierSystem.cs
new file mode 100644
index 00000000000..93832b0a529
--- /dev/null
+++ b/Content.Server/_White/Genetics/DNAModifierSystem.cs
@@ -0,0 +1,234 @@
+using Content.Server._White.Genetics.Components;
+using Content.Server.DeviceLinking.Systems;
+using Content.Server.Power.EntitySystems;
+using Content.Server.Speech.Components;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Body.Components;
+using Content.Shared.Climbing.Systems;
+using Content.Shared.Destructible;
+using Content.Shared.DeviceLinking.Events;
+using Content.Shared.DragDrop;
+using Content.Shared.Humanoid;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Movement.Events;
+using Content.Shared.Verbs;
+using Robust.Server.Containers;
+using Robust.Shared.Containers;
+using static Content.Shared.DNAModifier.SharedDNAModifierComponent;
+
+namespace Content.Server._White.Genetics;
+
+public sealed class DNAModifierSystem : EntitySystem
+{
+ [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
+ [Dependency] private readonly ActionBlockerSystem _blocker = default!;
+ [Dependency] private readonly ClimbSystem _climbSystem = default!;
+ [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!;
+ [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
+ [Dependency] private readonly ContainerSystem _containerSystem = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+
+ private const float UpdateRate = 1f;
+ private float _updateDif;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnRelayMovement);
+ SubscribeLocalEvent>(AddInsertOtherVerb);
+ SubscribeLocalEvent>(AddAlternativeVerbs);
+ SubscribeLocalEvent(OnDestroyed);
+ SubscribeLocalEvent(OnDragDropOn);
+ SubscribeLocalEvent(OnPortDisconnected);
+ SubscribeLocalEvent(OnAnchorChanged);
+ SubscribeLocalEvent(OnCanDragDropOn);
+ }
+
+ private void OnCanDragDropOn(EntityUid uid, DNAModifierComponent component, ref CanDropTargetEvent args)
+ {
+ args.Handled = true;
+ args.CanDrop |= CanModifierInsert(uid, args.Dragged, component);
+ }
+ public bool CanModifierInsert(EntityUid uid, EntityUid target, DNAModifierComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return false;
+
+ return HasComp(target);
+ }
+
+ private void OnComponentInit(EntityUid uid, DNAModifierComponent scannerComponent, ComponentInit args)
+ {
+ base.Initialize();
+ scannerComponent.BodyContainer = _containerSystem.EnsureContainer(uid, $"scanner-bodyContainer");
+ _signalSystem.EnsureSinkPorts(uid, DNAModifierComponent.ScannerPort);
+ }
+
+ private void OnRelayMovement(EntityUid uid, DNAModifierComponent scannerComponent, ref ContainerRelayMovementEntityEvent args)
+ {
+ if (!_blocker.CanInteract(args.Entity, uid))
+ return;
+
+ EjectBody(uid, scannerComponent);
+ }
+ private void AddInsertOtherVerb(EntityUid uid, DNAModifierComponent component, GetVerbsEvent args)
+ {
+ if (args.Using == null ||
+ !args.CanAccess ||
+ !args.CanInteract ||
+ IsOccupied(component) ||
+ !CanModifierInsert(uid, args.Using.Value, component))
+ return;
+
+ var name = "Unknown";
+ if (TryComp(args.Using.Value, out var metadata))
+ name = metadata.EntityName;
+
+ InteractionVerb verb = new()
+ {
+ Act = () => InsertBody(uid, args.Target, component),
+ Category = VerbCategory.Insert,
+ Text = name
+ };
+ args.Verbs.Add(verb);
+ }
+ private void AddAlternativeVerbs(EntityUid uid, DNAModifierComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanAccess || !args.CanInteract)
+ return;
+
+ // Eject verb
+ if (IsOccupied(component))
+ {
+ AlternativeVerb verb = new()
+ {
+ Act = () => EjectBody(uid, component),
+ Category = VerbCategory.Eject,
+ Text = Loc.GetString("dna-modifier-verb-noun-occupant"),
+ Priority = 1 // Promote to top to make ejecting the ALT-click action
+ };
+ args.Verbs.Add(verb);
+ }
+ }
+ private void OnDestroyed(EntityUid uid, DNAModifierComponent scannerComponent, DestructionEventArgs args)
+ {
+ EjectBody(uid, scannerComponent);
+ }
+
+ private void OnDragDropOn(EntityUid uid, DNAModifierComponent scannerComponent, ref DragDropTargetEvent args)
+ {
+ InsertBody(uid, args.Dragged, scannerComponent);
+
+ }
+ private void OnPortDisconnected(EntityUid uid, DNAModifierComponent component, PortDisconnectedEvent args)
+ {
+ component.ConnectedConsole = null;
+ }
+
+ private void OnAnchorChanged(EntityUid uid, DNAModifierComponent component, ref AnchorStateChangedEvent args)
+ {
+ if (component.ConnectedConsole == null || !TryComp(component.ConnectedConsole, out var console))
+ return;
+
+ if (args.Anchored)
+ {
+ _cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, uid, console);
+ return;
+ }
+ _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console);
+ }
+ private DNAModifierStatus GetStatus(EntityUid uid, DNAModifierComponent scannerComponent)
+ {
+ if (this.IsPowered(uid, EntityManager))
+ {
+ var body = scannerComponent.BodyContainer.ContainedEntity;
+ if (body == null)
+ return DNAModifierStatus.Open;
+
+ if (!TryComp(body.Value, out var state))
+ { // Is not alive or dead or critical
+ return DNAModifierStatus.Yellow;
+ }
+
+ return GetStatusFromDamageState(body.Value, state);
+ }
+ return DNAModifierStatus.Off;
+ }
+ public static bool IsOccupied(DNAModifierComponent scannerComponent)
+ {
+ return scannerComponent.BodyContainer.ContainedEntity != null;
+ }
+ private DNAModifierStatus GetStatusFromDamageState(EntityUid uid, MobStateComponent state)
+ {
+ if (_mobStateSystem.IsAlive(uid, state))
+ return DNAModifierStatus.Green;
+
+ if (_mobStateSystem.IsCritical(uid, state))
+ return DNAModifierStatus.Red;
+
+ if (_mobStateSystem.IsDead(uid, state))
+ return DNAModifierStatus.Death;
+
+ return DNAModifierStatus.Yellow;
+ }
+ private void UpdateAppearance(EntityUid uid, DNAModifierComponent scannerComponent)
+ {
+ if (TryComp(uid, out var appearance))
+ {
+ _appearance.SetData(uid, DNAModifierVisual.Status, GetStatus(uid, scannerComponent), appearance);
+ }
+ }
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ _updateDif += frameTime;
+ if (_updateDif < UpdateRate)
+ return;
+
+ _updateDif -= UpdateRate;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var scanner))
+ {
+ UpdateAppearance(uid, scanner);
+ }
+ }
+ public void InsertBody(EntityUid uid, EntityUid to_insert, DNAModifierComponent? scannerComponent)
+ {
+ if (!Resolve(uid, ref scannerComponent))
+ return;
+
+ if (scannerComponent.BodyContainer.ContainedEntity != null)
+ return;
+
+ if (!HasComp(to_insert))
+ return;
+
+ if(HasComp(to_insert))
+ return;
+
+ if (!HasComp(uid) && !HasComp(to_insert))
+ return;
+
+ _containerSystem.Insert(to_insert, scannerComponent.BodyContainer);
+ AddComp(to_insert);
+ UpdateAppearance(uid, scannerComponent);
+ }
+ public void EjectBody(EntityUid uid, DNAModifierComponent? scannerComponent)
+ {
+ if (!Resolve(uid, ref scannerComponent))
+ return;
+
+ if (scannerComponent.BodyContainer.ContainedEntity is not { Valid: true } contained)
+ return;
+
+ _containerSystem.Remove(contained, scannerComponent.BodyContainer);
+ _climbSystem.ForciblySetClimbing(contained, uid);
+ RemCompDeferred(contained);
+ UpdateAppearance(uid, scannerComponent);
+ }
+}
diff --git a/Content.Server/_White/Genetics/GenomeChangedEvent.cs b/Content.Server/_White/Genetics/GenomeChangedEvent.cs
new file mode 100644
index 00000000000..1fc69f6409b
--- /dev/null
+++ b/Content.Server/_White/Genetics/GenomeChangedEvent.cs
@@ -0,0 +1,18 @@
+using System.Collections;
+using Content.Server._White.Genetics.Components;
+
+namespace Content.Server._White.Genetics;
+
+public sealed class GenomeChangedEvent : EntityEventArgs
+{
+ public EntityUid Uid;
+ public GenomeComponent Comp = default!;
+ public Dictionary RegionsChanged = default!;
+
+ public GenomeChangedEvent(EntityUid uid, GenomeComponent comp, Dictionary regions)
+ {
+ Uid = uid;
+ Comp = comp;
+ RegionsChanged = regions;
+ }
+}
diff --git a/Content.Server/_White/Genetics/GenomePrototype.cs b/Content.Server/_White/Genetics/GenomePrototype.cs
new file mode 100644
index 00000000000..52aac028212
--- /dev/null
+++ b/Content.Server/_White/Genetics/GenomePrototype.cs
@@ -0,0 +1,25 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Server._White.Genetics;
+
+///
+/// Genome for an organism.
+/// Internally represented as a bit array, shown to players as bases using .
+/// Other systems can get data using and .
+/// Each bit can either be a boolean or be part of a number, which has its bits stored sequentially.
+/// Each species has its own unique genome layout that maps bits to data, which is randomized roundstart.
+/// Variable length information such as a list of reagents cannot be stored here.
+///
+[Prototype("genome")]
+public sealed partial class GenomePrototype : IPrototype
+{
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ ///
+ /// Names and bit lengths of each value in the genome.
+ /// If length is 1 then it will be a bool.
+ ///
+ [DataField("valuebits", required: true)]
+ public Dictionary ValueBits = new();
+}
diff --git a/Content.Server/_White/Genetics/MutationCollectionPrototype.cs b/Content.Server/_White/Genetics/MutationCollectionPrototype.cs
new file mode 100644
index 00000000000..32fc4bfa2c5
--- /dev/null
+++ b/Content.Server/_White/Genetics/MutationCollectionPrototype.cs
@@ -0,0 +1,21 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Server._White.Genetics
+{
+ ///
+ /// This is a prototype for a mutation pool.
+ ///
+ [Prototype("mutationCollection")]
+ public sealed partial class MutationCollectionPrototype : IPrototype
+ {
+ ///
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ ///
+ /// List of Ids of mutations in the collection.
+ ///
+ [DataField("mutations", required: true)]
+ public IReadOnlyList Mutations { get; private set; } = Array.Empty();
+ }
+}
diff --git a/Content.Server/_White/Genetics/MutationEffect.cs b/Content.Server/_White/Genetics/MutationEffect.cs
new file mode 100644
index 00000000000..04024f6f7e9
--- /dev/null
+++ b/Content.Server/_White/Genetics/MutationEffect.cs
@@ -0,0 +1,12 @@
+using JetBrains.Annotations;
+
+namespace Content.Server._White.Genetics
+{
+ [ImplicitDataDefinitionForInheritors]
+ [MeansImplicitUse]
+ public abstract partial class MutationEffect
+ {
+ public abstract void Apply(EntityUid appliedEntity, IEntityManager entityManager);
+ public abstract void Cancel(EntityUid appliedEntity, IEntityManager entityManager);
+ }
+}
diff --git a/Content.Server/_White/Genetics/MutationPrototype.cs b/Content.Server/_White/Genetics/MutationPrototype.cs
new file mode 100644
index 00000000000..3662d5f5f31
--- /dev/null
+++ b/Content.Server/_White/Genetics/MutationPrototype.cs
@@ -0,0 +1,33 @@
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server._White.Genetics;
+
+///
+/// This is a prototype for mutation
+///
+[Prototype(type: "mutation"), PublicAPI]
+public sealed partial class MutationPrototype : IPrototype
+{
+ ///
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ [DataField("name", required: true)]
+ public string Name { get; private set; } = default!;
+
+ ///
+ /// Instability that brings this mutation if add via mutator.
+ ///
+ [DataField("instability", required: false)]
+ public int Instability { get; private set; } = 0;
+
+ ///
+ /// Length of genetic sequence for this mutation.
+ ///
+ [DataField("length", required: true)]
+ public int Length { get; private set; } = 64;
+
+ [DataField("effects")]
+ public MutationEffect[] Effect = default!;
+}
diff --git a/Content.Server/_White/Genetics/Mutations/GlowingMutation.cs b/Content.Server/_White/Genetics/Mutations/GlowingMutation.cs
new file mode 100644
index 00000000000..8b7c5780d9c
--- /dev/null
+++ b/Content.Server/_White/Genetics/Mutations/GlowingMutation.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Movement.Systems;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+
+namespace Content.Server._White.Genetics.Mutations
+{
+ [UsedImplicitly]
+ [DataDefinition]
+ public sealed partial class GlowingMutation : MutationEffect
+ {
+ // Issues will arise if mutation is added to already glowing entity.
+ public override void Apply(EntityUid uid, IEntityManager entityManager)
+ {
+ var light = new PointLightComponent();
+ entityManager.AddComponent(uid, light);
+ }
+
+ public override void Cancel(EntityUid uid, IEntityManager entityManager)
+ {
+ entityManager.RemoveComponent(uid);
+ }
+ }
+}
diff --git a/Content.Server/_White/Genetics/RemoveMutationsReagentEffect.cs b/Content.Server/_White/Genetics/RemoveMutationsReagentEffect.cs
new file mode 100644
index 00000000000..c293e301cc8
--- /dev/null
+++ b/Content.Server/_White/Genetics/RemoveMutationsReagentEffect.cs
@@ -0,0 +1,44 @@
+using System.Text.RegularExpressions;
+using Content.Server._White.Genetics.Components;
+using Content.Server._White.Genetics.Systems;
+using Content.Shared.Body.Prototypes;
+using Content.Shared.Chemistry.Reagent;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server._White.Genetics
+{
+ [UsedImplicitly]
+ public sealed partial class RemoveMutationsReagentEffect : ReagentEffect
+ {
+
+ // TODO: add probability
+ public override void Effect(ReagentEffectArgs args)
+ {
+ if (!args.EntityManager.TryGetComponent(args.SolutionEntity, out var genome))
+ return;
+
+ var genetics = args.EntityManager.EntitySysManager.GetEntitySystem();
+
+ foreach (var mutation in genome.MutatedMutations)
+ {
+ genetics.CancelMutatorMutation(args.SolutionEntity, genome, mutation);
+ }
+
+ foreach (var mutation in genome.ActivatedMutations)
+ {
+ genetics.CancelActivatorMutation(args.SolutionEntity, genome, mutation);
+ }
+ }
+
+ // TODO
+ protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ //return Loc.GetString("reagent-effect-guidebook-adjust-reagent-reagent",("chance", Probability), ("deltasign", MathF.Sign(Amount.Float())), ("reagent", reagentProto.LocalizedName), ("amount", MathF.Abs(Amount.Float())));
+ //throw new NotImplementedException();
+ return "Hello";
+ }
+ }
+}
+
diff --git a/Content.Server/_White/Genetics/Systems/DNAScannerSystem.cs b/Content.Server/_White/Genetics/Systems/DNAScannerSystem.cs
new file mode 100644
index 00000000000..9c0b316747c
--- /dev/null
+++ b/Content.Server/_White/Genetics/Systems/DNAScannerSystem.cs
@@ -0,0 +1,96 @@
+using Content.Server._White.Genetics.Components;
+using Content.Server.Chemistry.Containers.EntitySystems;
+using Content.Server.Forensics;
+using Content.Server.PowerCell;
+using Content.Shared._White.Genetics;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Content.Shared.Mobs.Components;
+using Content.Shared.PowerCell;
+using Robust.Server.GameObjects;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Player;
+
+namespace Content.Server._White.Genetics.Systems;
+
+///
+/// This handles...
+///
+public sealed class DNAScannerSystem : EntitySystem
+{
+ [Dependency] private readonly PowerCellSystem _cell = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+ [Dependency] private readonly TransformSystem _transformSystem = default!;
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnAfterInteract);
+ SubscribeLocalEvent(OnDoAfter);
+ }
+
+ ///
+ /// Trigger the doafter for scanning
+ ///
+ private void OnAfterInteract(Entity uid, ref AfterInteractEvent args)
+ {
+ if (args.Target == null || !args.CanReach || !HasComp(args.Target) || !_cell.HasDrawCharge(uid, user: args.User))
+ return;
+
+ _audio.PlayPvs(uid.Comp.ScanningBeginSound, uid);
+
+ _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, uid.Comp.ScanDelay, new DNAScannerDoAfterEvent(), uid, target: args.Target, used: uid)
+ {
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ NeedHand = true
+ });
+ }
+
+ private void OnDoAfter(Entity uid, ref DNAScannerDoAfterEvent args)
+ {
+ if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User))
+ return;
+
+ _audio.PlayPvs(uid.Comp.ScanningEndSound, uid);
+
+ if (!TryComp(args.Target, out var genome))
+ return;
+
+ uid.Comp.ScannedGenome = genome;
+
+ OpenUserInterface(args.User, uid);
+
+ if (!_uiSystem.TryGetUi(uid.Owner, DNAScannerUiKey.Key, out var ui))
+ return;
+
+ string? fingerPrints = null;
+ if (TryComp(args.Target, out var dna))
+ fingerPrints = dna.DNA;
+
+ _uiSystem.SendUiMessage(ui, new DNAScannerScannedGenomeMessage(
+ GetNetEntity(args.Target),
+ genome.Genome,
+ genome.Layout,
+ genome.MutationRegions,
+ genome.MutatedMutations,
+ genome.ActivatedMutations,
+ fingerPrints
+ ));
+
+ args.Handled = true;
+ }
+
+ private void OpenUserInterface(EntityUid user, EntityUid scanner)
+ {
+ if (!TryComp(user, out var actor) || !_uiSystem.TryGetUi(scanner, DNAScannerUiKey.Key, out var ui))
+ return;
+
+ _uiSystem.OpenUi(ui, actor.PlayerSession);
+ }
+
+
+
+}
diff --git a/Content.Server/_White/Genetics/Systems/GeneticInjectorSystem.cs b/Content.Server/_White/Genetics/Systems/GeneticInjectorSystem.cs
new file mode 100644
index 00000000000..14c208422eb
--- /dev/null
+++ b/Content.Server/_White/Genetics/Systems/GeneticInjectorSystem.cs
@@ -0,0 +1,113 @@
+using Content.Server._White.Genetics.Components;
+using Content.Server.DoAfter;
+using Content.Server.Popups;
+using Content.Shared._White.Genetics;
+using Content.Shared.DoAfter;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Interaction;
+using Content.Shared.Mobs.Systems;
+using Content.Shared._White.Genetics.Components;
+
+namespace Content.Server._White.Genetics.Systems;
+
+public sealed class GeneticInjectorSystem : SharedGeneticInjectorSystem
+{
+ [Dependency] private readonly GenomeSystem _genome = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+ [Dependency] private readonly DoAfterSystem _doAfter = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnInjectDoAfterComplete);
+ SubscribeLocalEvent(OnAfterInteract);
+ }
+
+
+ // TODO: log
+ public void OnAfterInteract(Entity entity, ref AfterInteractEvent args)
+ {
+ if (args.Handled || !args.CanReach || args.Target == null)
+ return;
+
+ if (!TryComp(args.Target.Value, out var targetGenome))
+ {
+ if (!entity.Comp.Forced)
+ {
+ return;
+ }
+
+ targetGenome = new GenomeComponent();
+ AddComp(args.Target.Value, targetGenome);
+ }
+
+ if (!entity.Comp.Used)
+ return;
+
+ if (entity.Comp.MutationProtos.Count + entity.Comp.ActivatorMutations.Count == 0)
+ return;
+
+ var delay = entity.Comp.UseDelay;
+
+ if (args.User != args.Target.Value)
+ {
+ // Create a pop-up for the target
+ var userName = Identity.Entity(args.User, EntityManager);
+ _popup.PopupEntity(Loc.GetString("injector-component-injecting-target",
+ ("user", userName)), args.User, args.Target.Value);
+
+ // Check if the target is incapacitated or in combat mode and modify time accordingly.
+ if (_mobState.IsIncapacitated(args.Target.Value))
+ {
+ delay /= 2.5f;
+ }
+ }
+ else
+ {
+ delay /= 2.5f;
+ }
+
+ // TODO: admin log here
+ _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, delay, new GeneticInjectorDoAfterEvent(),
+ entity.Owner, target: args.Target.Value, used: entity.Owner)
+ {
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ BreakOnTargetMove = true,
+ MovementThreshold = 0.1f,
+ });
+ }
+
+ public void OnInjectDoAfterComplete(Entity injector, ref GeneticInjectorDoAfterEvent args)
+ {
+ if (args.Cancelled || args.Handled || args.Target == null)
+ return;
+
+ if (!TryComp(args.Target.Value, out var targetGenome))
+ return;
+
+ var mutationList = new List();
+
+ // mutator mutations
+ foreach (var mutation in injector.Comp.MutationProtos)
+ {
+ _genome.ApplyMutatorMutation(args.Target.Value, targetGenome, mutation);
+ mutationList.Add(mutation);
+ }
+
+ // activator mutations
+ foreach (var mutation in injector.Comp.ActivatorMutations)
+ {
+ _genome.ApplyActivatorMutation(args.Target.Value, targetGenome, mutation);
+ mutationList.Add(mutation);
+ }
+
+ injector.Comp.MutationProtos.Clear(); // TODO: probably need another way to make injectors single-use
+ injector.Comp.ActivatorMutations.Clear();
+ injector.Comp.Used = true;
+
+
+
+ // TODO: admin log here, use mutationList
+ }
+}
diff --git a/Content.Server/_White/Genetics/Systems/GenomeSystem.Instability.cs b/Content.Server/_White/Genetics/Systems/GenomeSystem.Instability.cs
new file mode 100644
index 00000000000..c0cc1b7686a
--- /dev/null
+++ b/Content.Server/_White/Genetics/Systems/GenomeSystem.Instability.cs
@@ -0,0 +1,23 @@
+using Content.Server._White.Genetics.Components;
+
+namespace Content.Server._White.Genetics.Systems;
+
+public sealed partial class GenomeSystem
+{
+ public void CheckInstability(EntityUid uid, GenomeComponent comp, int delta)
+ {
+ if (delta > 0)
+ {
+ if (comp.Instability < 100)
+ return;
+
+ // если эффекты уже готовятся примениться, но сущность себе еще мутации колит - надо отменить подготовку, пересчитать и применить новые
+
+ // TODO: нужна прикольная функция плотности вероятности p(instability, x), где х - "плохость" эффекта
+ // ну или другой способ задать распределение для произвольного количества эффектов с произвольными показателями плохости и для произвольной 200 (?) > Instability > 100
+ }
+
+
+
+ }
+}
diff --git a/Content.Server/_White/Genetics/Systems/GenomeSystem.Mutations.cs b/Content.Server/_White/Genetics/Systems/GenomeSystem.Mutations.cs
new file mode 100644
index 00000000000..5c3abb91da5
--- /dev/null
+++ b/Content.Server/_White/Genetics/Systems/GenomeSystem.Mutations.cs
@@ -0,0 +1,171 @@
+using Content.Server._White.Genetics.Components;
+
+namespace Content.Server._White.Genetics.Systems;
+
+///
+/// Apply Mutation Apply?
+///
+public sealed partial class GenomeSystem
+{
+ ///
+ /// TODO: recheck
+ ///
+ ///
+ private void OnGenomeChanged(GenomeChangedEvent args)
+ {
+ foreach (var (region, (was, became)) in args.RegionsChanged)
+ {
+ if (!args.Comp.Layout.Values.TryGetValue(region, out var indexes))
+ continue;
+
+ if (!args.Comp.MutationRegions.TryGetValue(region, out var possibleMutation))
+ continue;
+
+ if (!_mutations.TryGetValue(possibleMutation, out var mutation))
+ continue;
+
+
+ if (args.Comp.ActivatedMutations.Contains(possibleMutation))
+ {
+ if (args.Comp.Genome.GetInt(indexes.Item1, indexes.Item2) !=
+ mutation.Genome.GetInt(0, mutation.Genome.GetLength()))
+ {
+ CancelActivatorMutation(args.Uid, args.Comp, possibleMutation);
+ }
+ }
+ else
+ {
+ if (args.Comp.Genome.GetInt(indexes.Item1, indexes.Item2) ==
+ mutation.Genome.GetInt(0, mutation.Genome.GetLength()))
+ {
+ ApplyActivatorMutation(args.Uid, args.Comp, possibleMutation);
+ }
+ }
+
+ //TODO: incorporate mutator mutations?
+ }
+ }
+
+ public void ApplyMutatorMutation(EntityUid uid, GenomeComponent comp, string mutationName)
+ {
+ if (!_mutations.TryGetValue(mutationName, out var mutation))
+ return;
+
+ comp.Instability += mutation.Instability;
+ comp.MutatedMutations.Add(mutationName);
+ foreach (var effect in mutation.Effects)
+ {
+ effect.Apply(uid, EntityManager);
+ }
+
+ CheckInstability(uid, comp, mutation.Instability);
+ }
+
+ public void CancelMutatorMutation(EntityUid uid, GenomeComponent comp, string mutationName)
+ {
+ if (!comp.MutatedMutations.Contains(mutationName) || _mutations.TryGetValue(mutationName, out var mutation))
+ return;
+
+ comp.MutatedMutations.Remove(mutationName);
+ comp.Instability -= mutation.Instability;
+ foreach (var effect in mutation.Effects)
+ {
+ effect.Cancel(uid, EntityManager);
+ }
+
+ CheckInstability(uid, comp, -mutation.Instability);
+ }
+
+ ///
+ /// Applies the mutation indefinitely if entity has the corresponding region. sets that region
+ ///
+ ///
+ ///
+ ///
+ public void ApplyActivatorMutation(EntityUid uid, GenomeComponent comp, string mutationName)
+ {
+ if (comp.ActivatedMutations.Contains(mutationName))
+ return;
+
+ if (!_mutations.TryGetValue(mutationName, out var mutation))
+ return;
+
+ if (!comp.MutationRegions.ContainsValue(mutationName))
+ return;
+
+ var activated = false;
+ foreach (var region in comp.MutationRegions.Keys)
+ {
+ if (mutationName != comp.MutationRegions[region])
+ continue;
+
+ comp.Layout.SetBitArray(comp.Genome, region, mutation.Genome.Bits); // this should probably vary depending on context
+
+ if (activated)
+ continue;
+
+ comp.ActivatedMutations.Add(mutationName);
+ foreach (var effect in mutation.Effects)
+ {
+ effect.Apply(uid, EntityManager);
+ }
+
+ activated = true;
+ }
+ }
+
+ public void CancelActivatorMutation(EntityUid uid, GenomeComponent comp, string mutationName)
+ {
+ if (!comp.ActivatedMutations.Contains(mutationName))
+ return;
+
+ if (!_mutations.TryGetValue(mutationName, out var mutation))
+ return;
+
+ foreach (var effect in mutation.Effects)
+ {
+ effect.Cancel(uid, EntityManager);
+ }
+
+ comp.ActivatedMutations.Remove(mutationName);
+ }
+
+
+ // TODO: снести ес
+ /*
+ public int GetDamage(string race)
+ {
+ var races = new Dictionary()
+ {
+ { "Arachnid", 5},
+ { "Diona", 5},
+ { "Dwarf", 5},
+ { "Human", 5}
+ };
+
+ //TODO: больше рас
+
+ return races[race];
+ }
+
+ public void SuperStrength(EntityUid uid, GenomeComponent genomeComp, bool state)
+ {
+ if (!HasComp(uid))
+ return;
+
+ if (state == true)
+ {
+ TryComp(uid, out var comp);
+ comp?.Damage.DamageDict.Add("Blunt", 30);
+
+ genomeComp.ActivatedMutations.Add("SuperStrength");
+ }
+ else
+ {
+ TryComp(uid, out var comp);
+ comp?.Damage.DamageDict.Add("Blunt", 5);
+
+ genomeComp.ActivatedMutations.Remove("SuperStrength");
+ }
+ } */
+}
diff --git a/Content.Server/_White/Genetics/Systems/GenomeSystem.cs b/Content.Server/_White/Genetics/Systems/GenomeSystem.cs
new file mode 100644
index 00000000000..eeacd67a447
--- /dev/null
+++ b/Content.Server/_White/Genetics/Systems/GenomeSystem.cs
@@ -0,0 +1,228 @@
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server._White.Genetics.Components;
+using Content.Server.GameTicking.Events;
+using Content.Shared._White.Genetics;
+using Content.Shared.Damage;
+using Content.Shared.Flash.Components;
+using Content.Shared.GameTicking;
+using Content.Shared.Humanoid;
+using Content.Shared.Radiation.Events;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server._White.Genetics.Systems;
+
+///
+/// Assigns each a random roundstart.
+///
+public sealed partial class GenomeSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly ILogManager _log = default!;
+
+
+ protected ISawmill _sawmill = default!;
+ // This is where all the genome layouts are stored.
+ // TODO: store on round entity when thats done, so persistence reloading doesnt scramble genes
+ [ViewVariables]
+ private readonly Dictionary _layouts = new();
+
+ private string _mutationsPool = "StandardHumanMutations";
+ private bool _mutationsInitialized = false;
+ private Dictionary _mutations = new ();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGenomeCompInit);
+ SubscribeLocalEvent(Reset);
+ SubscribeLocalEvent(OnGenomeChanged);
+ SubscribeLocalEvent(OnIrradiated);
+ InitializeMutations();
+ _sawmill = _log.GetSawmill("genetics");
+ }
+
+
+ ///
+ /// TODO: test this whole thing
+ ///
+ ///
+ ///
+ ///
+ private void OnGenomeCompInit(EntityUid uid, GenomeComponent comp, ComponentInit args)
+ {
+ // only empty in test and when vving
+ if (comp.GenomeId != string.Empty)
+ comp.Layout = GetOrCreateLayout(comp.GenomeId);
+
+ var mutationsPool = _mutations;
+ foreach (var (name, (index, len)) in comp.Layout.Values)
+ {
+ if (!name.Contains("mutation"))
+ continue;
+
+ var mutationName = mutationsPool.Keys.ToArray()[_random.Next(mutationsPool.Count)];
+
+ var (genome, effect, _) = mutationsPool[mutationName]; //TODO: are mutations standardised in size?
+ var bits = genome.Bits;
+ if (bits.Length == 0)
+ {
+ _sawmill.Error($"Error while initializing a sequence in GenomeComponent. Name: {name}; Length: {len}");
+ //throw new Exception()
+ continue;
+ }
+ var mutatedBits = Array.Empty();
+
+ var prob = 0.99f;
+ while (prob > 0.001f)
+ {
+ if (!_random.Prob(prob))
+ {
+ if (prob == 0.99f)
+ {
+ ApplyMutatorMutation(uid, comp, mutationName);
+ }
+ break;
+ }
+
+ var i = _random.Next(len);
+
+ if (mutatedBits.Contains(i))
+ {
+ break;
+ }
+
+ bits[i] = !bits[i];
+ mutatedBits.Append(i);
+
+ prob = prob / 2;
+ }
+
+ mutationsPool.Remove(mutationName);
+ comp.Layout.SetBitArray(comp.Genome, name, bits);
+ comp.MutationRegions.Add(name, mutationName);
+ }
+ }
+
+
+ ///
+ /// TODO: Test this shit
+ ///
+ ///
+ ///
+ ///
+ public void OnIrradiated(EntityUid uid, GenomeComponent comp, OnIrradiatedEvent args)
+ {
+ if (args.TotalRads > 20)
+ {
+ // TODO: change genome, apply random mutation via mutator
+ }
+ }
+
+ private void Reset(RoundRestartCleanupEvent args)
+ {
+ _layouts.Clear();
+ _mutations.Clear();
+ _mutationsInitialized = false;
+ }
+
+ private void InitializeMutations()
+ {
+ _proto.TryIndex(_mutationsPool, out var pool);
+ if (pool == null)
+ {
+ //TODO: throw an error here
+ return;
+ }
+
+ foreach (var mutation in pool.Mutations)
+ {
+ _proto.TryIndex(mutation, out var mutationProto);
+ if (mutationProto != null)
+ _mutations.Add(mutationProto.Name, (GenerateSomeRandomGeneticSequenceAndCheckIfItIsIn_mutationsFunction(mutationProto.Length), mutationProto.Effect, mutationProto.Instability));
+ }
+
+ _mutationsInitialized = true;
+ }
+
+ public Genome GenerateSomeRandomGeneticSequenceAndCheckIfItIsIn_mutationsFunction(int length, int cycle = 0)
+ {
+ var sequence = new Genome(length);
+ sequence.Mutate(0, length, 0.5f);
+
+ if (cycle >= 10) // i think 10 cycles is enough
+ {
+ _sawmill.Error("ActivatedMutations initialization error. Try making longer sequences or less mutations");
+ return sequence;
+ }
+
+ foreach (var (_, (seq, _, _)) in _mutations)
+ {
+ if (sequence == seq)
+ return GenerateSomeRandomGeneticSequenceAndCheckIfItIsIn_mutationsFunction(length, cycle + 1);
+ }
+ return sequence;
+ }
+
+ ///
+ /// Either gets an existing genome layout or creates a new random one.
+ /// Genome layouts are reset between rounds.
+ /// Anything with calls this on mapinit to ensure it uses the correct layout.
+ ///
+ /// Genome prototype id to create the layout from
+ public GenomeLayout GetOrCreateLayout(string id)
+ {
+ // already created for this round so just use it
+ if (TryGetLayout(id, out var layout))
+ return layout;
+
+ // prototype must exist
+ var proto = _proto.Index(id);
+
+ // create the new random genome layout!
+ layout = new GenomeLayout();
+ var names = new List(proto.ValueBits.Keys);
+ _random.Shuffle(names);
+ foreach (var name in names)
+ {
+ var length = proto.ValueBits[name];
+ layout.Add(name, length);
+ }
+
+ // save it for the rest of the round
+ AddLayout(id, layout);
+ return layout;
+ }
+
+ ///
+ /// Copies the Genome bits from a parent to a child.
+ /// They must use the same genome layout or it will be logged and copy nothing.
+ ///
+ public void CopyParentGenes(EntityUid uid, EntityUid parent, GenomeComponent? comp = null, GenomeComponent? parentComp = null)
+ {
+ if (!Resolve(uid, ref comp) || !Resolve(parent, ref parentComp))
+ return;
+
+ if (parentComp.GenomeId != comp.GenomeId)
+ {
+ Log.Error($"Tried to copy incompatible genome from {ToPrettyString(parent):parent)} ({parentComp.GenomeId}) to {ToPrettyString(uid):child)} ({comp.GenomeId})");
+ return;
+ }
+
+ parentComp.Genome.CopyTo(comp.Genome);
+ }
+
+ private bool TryGetLayout(string id, [NotNullWhen(true)] out GenomeLayout? layout)
+ {
+ return _layouts.TryGetValue(id, out layout);
+ }
+
+ private void AddLayout(string id, GenomeLayout layout)
+ {
+ _layouts.Add(id, layout);
+ }
+}
diff --git a/Content.Shared/_White/DNAConsole/SharedDNAConsole.cs b/Content.Shared/_White/DNAConsole/SharedDNAConsole.cs
new file mode 100644
index 00000000000..2f30dabec32
--- /dev/null
+++ b/Content.Shared/_White/DNAConsole/SharedDNAConsole.cs
@@ -0,0 +1,54 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DNAConsole
+{
+ [Serializable, NetSerializable]
+ public sealed class DNAConsoleBoundUserInterfaceState : BoundUserInterfaceState
+ {
+ public readonly string? ModifierBodyInfo;
+ public readonly ModifierStatus ModifierStatus;
+ public readonly bool ModifierConnected;
+ public readonly bool ModifierInRange;
+ public DNAConsoleBoundUserInterfaceState(string? scannerBodyInfo, ModifierStatus cloningStatus, bool scannerConnected, bool scannerInRange)
+ {
+ ModifierBodyInfo = scannerBodyInfo;
+ ModifierStatus = cloningStatus;
+ ModifierConnected = scannerConnected;
+ ModifierInRange = scannerInRange;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public enum ModifierStatus : byte
+ {
+ Ready,
+ ModifierEmpty,
+ ModifierOccupantAlive,
+ OccupantMetaphyiscal,
+ ModifierOccupied
+ }
+
+ [Serializable, NetSerializable]
+ public enum DNAConsoleUiKey : byte
+ {
+ Key
+ }
+
+ [Serializable, NetSerializable]
+ public enum UiButton : byte
+ {
+ Clone,
+ Eject
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class UiButtonPressedMessage : BoundUserInterfaceMessage
+ {
+ public readonly UiButton Button;
+
+ public UiButtonPressedMessage(UiButton button)
+ {
+ Button = button;
+ }
+ }
+}
diff --git a/Content.Shared/_White/DNAModifier/SharedDNAModifierComponent.cs b/Content.Shared/_White/DNAModifier/SharedDNAModifierComponent.cs
new file mode 100644
index 00000000000..f42eec7760c
--- /dev/null
+++ b/Content.Shared/_White/DNAModifier/SharedDNAModifierComponent.cs
@@ -0,0 +1,22 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.DNAModifier;
+
+public abstract partial class SharedDNAModifierComponent : Component
+{
+ [Serializable, NetSerializable]
+ public enum DNAModifierVisual : byte
+ {
+ Status
+ }
+ [Serializable, NetSerializable]
+ public enum DNAModifierStatus : byte
+ {
+ Off,
+ Open,
+ Red,
+ Death,
+ Green,
+ Yellow,
+ }
+}
diff --git a/Content.Shared/_White/Genetics/Components/GeneticInjectorComponent.cs b/Content.Shared/_White/Genetics/Components/GeneticInjectorComponent.cs
new file mode 100644
index 00000000000..37c1f755414
--- /dev/null
+++ b/Content.Shared/_White/Genetics/Components/GeneticInjectorComponent.cs
@@ -0,0 +1,39 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._White.Genetics.Components;
+
+public sealed partial class GeneticInjectorComponent : Component
+{
+ [DataField("mutatorMutations")]
+ public List MutationProtos = new List();
+
+ [DataField("activatorMutations")]
+ public List ActivatorMutations = new List();
+
+ [DataField("forced")]
+ public bool Forced = false;
+
+ [DataField("useDelay")]
+ public float UseDelay = 2.5f;
+
+ [DataField]
+ public bool Used = false;
+
+ ///
+ /// Sprite state to use if Used = false
+ ///
+ [DataField]
+ public string NewState = "new";
+
+ ///
+ /// Sprite state to use if Used = true
+ ///
+ [DataField]
+ public string UsedState = "used";
+}
+
+[Serializable, NetSerializable]
+public sealed partial class GeneticInjectorDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/_White/Genetics/DNAScannerDoAfterEvent.cs b/Content.Shared/_White/Genetics/DNAScannerDoAfterEvent.cs
new file mode 100644
index 00000000000..26a54f4e5a4
--- /dev/null
+++ b/Content.Shared/_White/Genetics/DNAScannerDoAfterEvent.cs
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._White.Genetics;
+
+[Serializable, NetSerializable]
+public sealed partial class DNAScannerDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Content.Shared/_White/Genetics/DNAScannerScannedGenomeMessage.cs b/Content.Shared/_White/Genetics/DNAScannerScannedGenomeMessage.cs
new file mode 100644
index 00000000000..15ed80baa2d
--- /dev/null
+++ b/Content.Shared/_White/Genetics/DNAScannerScannedGenomeMessage.cs
@@ -0,0 +1,35 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._White.Genetics;
+
+///
+/// On interacting with an entity retrieves the entity UID for use with getting the current damage of the mob.
+///
+[Serializable, NetSerializable]
+public sealed class DNAScannerScannedGenomeMessage : BoundUserInterfaceMessage
+{
+ public readonly NetEntity? TargetEntity;
+ public Genome Genome;
+ public GenomeLayout Layout;
+ public Dictionary MutationRegions;
+ public List MutatedMutations;
+ public List ActivatedMutations;
+ public string? FingerPrints;
+
+ public DNAScannerScannedGenomeMessage(NetEntity? targetEntity,
+ Genome genome,
+ GenomeLayout layout,
+ Dictionary mutationRegions,
+ List mutatedMutations,
+ List activatedMutations,
+ string? fingerPrints)
+ {
+ TargetEntity = targetEntity;
+ Genome = genome;
+ Layout = layout;
+ MutationRegions = mutationRegions;
+ MutatedMutations = mutatedMutations;
+ ActivatedMutations = activatedMutations;
+ FingerPrints = fingerPrints;
+ }
+}
diff --git a/Content.Shared/_White/Genetics/DNAScannerUiKey.cs b/Content.Shared/_White/Genetics/DNAScannerUiKey.cs
new file mode 100644
index 00000000000..f3fd5699e4d
--- /dev/null
+++ b/Content.Shared/_White/Genetics/DNAScannerUiKey.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._White.Genetics;
+
+///
+/// This handles...
+///
+[Serializable, NetSerializable]
+public enum DNAScannerUiKey : byte
+{
+ Key
+}
diff --git a/Content.Shared/_White/Genetics/GenesPrototype.cs b/Content.Shared/_White/Genetics/GenesPrototype.cs
new file mode 100644
index 00000000000..5f0e5d1341b
--- /dev/null
+++ b/Content.Shared/_White/Genetics/GenesPrototype.cs
@@ -0,0 +1,26 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._White.Genetics;
+
+///
+/// Genes for an organism, provides the actual values which are used to build bits.
+///
+[Prototype("genes")]
+public sealed partial class GenesPrototype : IPrototype
+{
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ ///
+ /// Each bool which will be set to true.
+ ///
+ [DataField]
+ public HashSet Bools = new();
+
+ ///
+ /// Values of each int to set.
+ /// Any unused bits are dropped silently.
+ ///
+ [DataField]
+ public Dictionary Ints = new();
+}
diff --git a/Content.Shared/_White/Genetics/Genome.cs b/Content.Shared/_White/Genetics/Genome.cs
new file mode 100644
index 00000000000..f25ba660add
--- /dev/null
+++ b/Content.Shared/_White/Genetics/Genome.cs
@@ -0,0 +1,203 @@
+using Robust.Shared.Serialization;
+using System.Collections;
+using System.Linq;
+using System.Text;
+using Robust.Shared.Random;
+
+namespace Content.Shared._White.Genetics;
+
+///
+/// Genome for an organism.
+/// Internally represented as a bit array, shown to players as bases using .
+/// Other systems can get data using and .
+/// Each bit can either be a boolean or be part of a number, which has its bits stored sequentially.
+/// Each species has its own unique genome layout that maps bits to data, which is randomized roundstart.
+/// Variable length information such as a list of reagents cannot be stored here.
+///
+[DataDefinition]
+public sealed partial class Genome
+{
+ [Dependency] private readonly IRobustRandom _robustRandom = default!;
+
+ ///
+ /// Bits that represent the genes bools and ints.
+ ///
+ [ViewVariables]
+ public BitArray Bits = new BitArray(0);
+
+ private static char[] Bases = new[] { 'A', 'C', 'G', 'T'};
+
+ ///
+ /// Creates a new genome from all zeroes.
+ ///
+ public Genome(int count = 0)
+ {
+ Bits = new BitArray(count);
+ }
+
+ ///
+ /// Copy this genome's bits to another one.
+ ///
+ public void CopyTo(Genome other)
+ {
+ other.Bits = new BitArray(Bits);
+ }
+
+ ///
+ /// Get the value of a single bool at an index.
+ /// If it is out of bounds false is returned.
+ ///
+ /// Bit index to get from
+ public bool GetBool(int index)
+ {
+ return index < Bits.Length && Bits[index];
+ }
+
+ ///
+ /// Get the value of an integer with multiple bits.
+ /// It should be clamped to a reasonable number afterwards.
+ ///
+ /// Starting bit index to get from
+ /// Number of bits to read
+ public int GetInt(int index, int bits)
+ {
+ var value = 0;
+ for (int i = 0; i < bits; i++)
+ {
+ var bit = 1 << i;
+ if (GetBool(index + i))
+ {
+ value |= bit;
+ }
+ }
+
+ return value;
+ }
+
+ ///
+ /// Return a base pair string for a range of bits in the genome.
+ ///
+ /// Starting bit index to get from
+ /// Number of bases to include in the string
+ public string GetBases(int index, int bases)
+ {
+ var builder = new StringBuilder(bases);
+ for (int i = 0; i < bases; i++)
+ {
+ // 2 bits makes a base
+ var c = Bases[GetInt(index + i * 2, 2)];
+ builder.Append(c);
+ }
+
+ return builder.ToString();
+ }
+
+ public int GetLength()
+ {
+ return Bits.Length;
+ }
+
+ ///
+ /// Sets a boolean value at a bit index to a value.
+ ///
+ /// Bit index to set
+ public void SetBool(int index, bool value)
+ {
+ Bits[index] = value;
+ }
+
+ ///
+ /// Sets the bits of an integer to a value.
+ /// This will truncate the integer to fit inside the possible values.
+ /// Also handles writing past the end correctly.
+ ///
+ /// Starting bit index to set
+ /// Number of bits to set
+ /// Number to set
+ public void SetInt(int index, int bits, int value)
+ {
+ for (int i = 0; i < bits; i++)
+ {
+ var bit = 1 << i;
+ Bits[index + i] = (value & bit) != 0;
+ }
+ }
+
+ ///
+ /// Flips a bit at an index from a 0 to a 1.
+ ///
+ /// Bit index to flip
+ public void FlipBit(int index)
+ {
+ Bits[index] = !Bits[index];
+ }
+
+ ///
+ /// Sets the 2 bits at an index to the value of a base.
+ ///
+ /// Bit index to set at
+ /// Base character to set
+ public void SetBase(int index, char c)
+ {
+ int value = 0;
+ switch (c)
+ {
+ case 'A':
+ value = 0;
+ break;
+ case 'C':
+ value = 1;
+ break;
+ case 'G':
+ value = 2;
+ break;
+ case 'T':
+ value = 3;
+ break;
+ }
+
+ SetInt(index, bits: 2, value: value);
+ }
+
+ ///
+ /// TODO: recheck
+ ///
+ ///
+ ///
+ public void SetBitArray(int index, BitArray array)
+ {
+ for (int i = index; i < array.Length; i++)
+ {
+ Bits[i] = array[i - index];
+ }
+ }
+
+ ///
+ /// Mutates a part of Genome from index to index + length. The more severity - the more mutated it will be.
+ /// May need to move it to a different system.
+ ///
+ ///
+ ///
+ ///
+ public void Mutate(int index, int length, float severity)
+ {
+ //float prob = severity; // This may be unnecessary, needs consideration.
+ severity = Math.Clamp(severity, 0, 1);
+
+ for (int i = 0; i < length; i++)
+ {
+ if (_robustRandom.Prob(severity))
+ FlipBit(index + i);
+ }
+ }
+
+ ///
+ /// Future function that will extend the genome (used for tg mutators). TODO
+ ///
+ ///
+ ///
+ public void Extend(int length, BitArray? array) // possibly more arguments
+ {
+
+ }
+}
diff --git a/Content.Shared/_White/Genetics/GenomeLayout.cs b/Content.Shared/_White/Genetics/GenomeLayout.cs
new file mode 100644
index 00000000000..e0191b02d96
--- /dev/null
+++ b/Content.Shared/_White/Genetics/GenomeLayout.cs
@@ -0,0 +1,109 @@
+using System.Collections;
+using Content.Shared._White.Genetics;
+using Robust.Shared.Utility;
+
+namespace Content.Shared._White.Genetics;
+
+///
+/// Maps a 's bits to bools and ints.
+/// Stores indices for every bool and int that can be retrieved.
+///
+public sealed class GenomeLayout
+{
+ ///
+ /// Indices and bit lengths of every value stored in the genome.
+ /// For bit length must be 1.
+ ///
+ [DataField]
+ public Dictionary Values = new();
+
+ ///
+ /// How many bits this genome has total
+ ///
+ [DataField]
+ public int TotalBits;
+
+ ///
+ /// Get a bool from the genome by name.
+ ///
+ public bool GetBool(Genome genome, string name)
+ {
+ var (index, bits) = Values[name];
+ DebugTools.Assert(bits == 1, "Do not use GetBool for int genome values");
+
+ return genome.GetBool(index);
+ }
+
+ ///
+ /// Get an int from the genome by name.
+ ///
+ public int GetInt(Genome genome, string name)
+ {
+ var (index, bits) = Values[name];
+ return genome.GetInt(index, bits);
+ }
+
+ ///
+ /// Sets a bool value on the genome by name.
+ ///
+ public void SetBool(Genome genome, string name, bool value)
+ {
+ var (index, bits) = Values[name];
+ DebugTools.Assert(bits == 1, "Do not use SetBool for int genome values");
+
+ genome.SetBool(index, value);
+ }
+
+ ///
+ /// Sets an int on the genome by name.
+ ///
+ ///
+ /// Unused bits are silently ignored.
+ ///
+ public void SetInt(Genome genome, string name, int value)
+ {
+ var (index, bits) = Values[name];
+ genome.SetInt(index, bits: bits, value: value);
+ }
+
+ ///
+ /// TODO: recheck
+ ///
+ ///
+ ///
+ ///
+ public void SetBitArray(Genome genome, string name, BitArray array)
+ {
+ var (index, bits) = Values[name];
+
+ if (bits != array.Length)
+ return;
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ genome.SetBool(index + i, array[i]);
+ }
+ }
+
+ ///
+ /// Add a mapping to the layout.
+ /// If length is 1 it will be a bool, int otherwise.
+ ///
+ /// Name of the value to add
+ /// Number of bits the value has
+ public void Add(string name, ushort bits)
+ {
+ DebugTools.Assert(bits > 0, "Genome value bit count must be positive");
+
+ var index = TotalBits;
+ Values.Add(name, (index, bits));
+ TotalBits += bits;
+ }
+
+ public void MutateLayout(Genome genome, string name, float severity)
+ {
+ var (index, length) = Values[name];
+
+ genome.Mutate(index, length, severity);
+ }
+}
diff --git a/Content.Shared/_White/Genetics/SharedGeneticInjectorSystem.cs b/Content.Shared/_White/Genetics/SharedGeneticInjectorSystem.cs
new file mode 100644
index 00000000000..22ec9e82cbd
--- /dev/null
+++ b/Content.Shared/_White/Genetics/SharedGeneticInjectorSystem.cs
@@ -0,0 +1,43 @@
+using Content.Shared._White.Genetics.Components;
+using Content.Shared.Administration.Logs;
+using Content.Shared.CombatMode;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Popups;
+
+namespace Content.Shared._White.Genetics;
+
+public partial class SharedGeneticInjectorSystem : EntitySystem
+{
+ [Dependency] protected readonly SharedPopupSystem _popup = default!;
+ [Dependency] protected readonly MobStateSystem _mobState = default!;
+ [Dependency] protected readonly SharedCombatModeSystem _combat = default!;
+ [Dependency] protected readonly SharedDoAfterSystem _doAfter = default!;
+ [Dependency] protected readonly ISharedAdminLogManager _adminLogger = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnInjectorStartup);
+ SubscribeLocalEvent(OnInjectorUse);
+ }
+
+ private void OnInjectorStartup(Entity entity, ref ComponentStartup args)
+ {
+ // ???? why ?????
+ // TODO: wtf is this
+ Dirty(entity);
+ }
+
+ private void OnInjectorUse(Entity entity, ref UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ // inject yourself here
+
+ args.Handled = true;
+ }
+
+
+}
diff --git a/Resources/Prototypes/White/genetic_prototypes.yml b/Resources/Prototypes/White/genetic_prototypes.yml
new file mode 100644
index 00000000000..276d0e0cd18
--- /dev/null
+++ b/Resources/Prototypes/White/genetic_prototypes.yml
@@ -0,0 +1,111 @@
+- type: wireLayout
+ id: DNAModifier
+ dummyWires: 2
+ wires:
+ - !type:PowerWireAction
+ - !type:PowerWireAction
+ pulseTimeout: 15
+
+- type: entity
+ parent: BaseComputerCircuitboard
+ id: DNAConsoleComputerCircuitboard
+ name: dna console computer board
+ description: A computer printed circuit board for a dna console.
+ components:
+ - type: Sprite
+ state: cpu_dna
+ - type: ComputerBoard
+ prototype: ComputerDNAConsole
+
+- type: entity
+ parent: BaseComputer
+ id: ComputerDNAConsole
+ name: dna console computer
+ description: soon
+ components:
+ - type: DNAConsole
+ - type: DeviceList
+ - type: DeviceNetwork
+ deviceNetId: Wired
+ - type: Sprite
+ layers:
+ - map: ["computerLayerBody"]
+ state: computer
+ - map: ["computerLayerKeyboard"]
+ state: generic_keyboard
+ - map: ["computerLayerScreen"]
+ state: dna
+ - map: ["computerLayerKeys"]
+ state: generic_keys
+ - type: ApcPowerReceiver
+ powerLoad: 3400 #We want this to fail first so I transferred most of the scanner and pod's power here. (3500 in total)
+ - type: Computer
+ board: DNAConsoleComputerCircuitboard
+ - type: PointLight
+ radius: 1.5
+ energy: 1.6
+ color: "#00908D"
+ - type: DeviceLinkSource
+ range: 4
+ ports:
+ - DNAModifierSender
+ - type: ActivatableUI
+ key: enum.DNAConsoleUiKey.Key
+ - type: UserInterface
+ interfaces:
+ - key: enum.DNAConsoleUiKey.Key
+ type: DNAConsoleBoundUserInterface
+ - type: Speech
+ speechVerb: Robotic
+ speechSounds: Pai
+ - type: Damageable
+ damageContainer: Inorganic
+ damageModifierSet: StrongMetallic
+
+- type: entity
+ id: DNAModifierMachineCircuitboard
+ parent: BaseMachineCircuitboard
+ name: dna modifier machine board
+ description: A machine printed circuit board for a dna modifier.
+ components:
+ - type: Sprite
+ state: dnamodifier
+ - type: MachineBoard
+ prototype: DNAModifier
+ requirements:
+ Capacitor: 1
+ materialRequirements:
+ Glass: 5
+ Cable: 1
+
+- type: sourcePort
+ id: DNAModifierSender
+ name: signal-port-name-dna-modifier-sender
+ description: signal-port-description-dna-modifier-sender
+
+- type: sinkPort
+ id: DNAModifierReceiver
+ name: signal-port-name-dna-modifier-receiver
+ description: signal-port-description-dna-modifier-receiver
+
+- type: genome
+ id: HumanGenomePrototype
+ valuebits:
+
+- type: mutationCollection
+ id: StandardHumanMutations
+ mutations:
+ - SuperStrength
+ - XrayVision
+
+- type: mutation
+ id: SuperStrength
+ name: Super Strength
+ instability: 10
+ length: 64
+
+- type: mutation
+ id: XrayVision
+ name: X-ray vision
+ instability: 30
+ length: 64
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/closed.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/closed.png
new file mode 100644
index 00000000000..b98dc797322
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/closed.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/closed_unpowered.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/closed_unpowered.png
new file mode 100644
index 00000000000..f1e49312923
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/closed_unpowered.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/idle_unlit.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/idle_unlit.png
new file mode 100644
index 00000000000..4470a4668cb
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/idle_unlit.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/maint_unlit.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/maint_unlit.png
new file mode 100644
index 00000000000..1726aa5d102
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/maint_unlit.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/meta.json b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/meta.json
new file mode 100644
index 00000000000..0ef80a0457e
--- /dev/null
+++ b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/meta.json
@@ -0,0 +1,67 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/1da0b5547e02db0db48d0bc93926c26bd8888347",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "open"
+ },
+ {
+ "name": "closed"
+ },
+ {
+ "name": "open_unpowered"
+ },
+ {
+ "name": "closed_unpowered"
+ },
+ {
+ "name": "occupied",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "off_unlit"
+ },
+ {
+ "name": "occupied_unlit"
+ },
+ {
+ "name": "idle_unlit",
+ "delays": [
+ [
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "maint_unlit",
+ "delays": [
+ [
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "red_unlit",
+ "delays": [
+ [
+ 0.2,
+ 0.2
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/occupied.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/occupied.png
new file mode 100644
index 00000000000..e2c9b1f968d
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/occupied.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/occupied_unlit.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/occupied_unlit.png
new file mode 100644
index 00000000000..0ff8021e00e
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/occupied_unlit.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/off_unlit.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/off_unlit.png
new file mode 100644
index 00000000000..445ddaae706
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/off_unlit.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/open.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/open.png
new file mode 100644
index 00000000000..c97816e322b
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/open.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/open_unpowered.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/open_unpowered.png
new file mode 100644
index 00000000000..00c8e1a80fa
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/open_unpowered.png differ
diff --git a/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/red_unlit.png b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/red_unlit.png
new file mode 100644
index 00000000000..16ca86aa362
Binary files /dev/null and b/Resources/Textures/White/Objects/Devices/dna_modifier.rsi/red_unlit.png differ