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