diff --git a/Content.Client/_RMC14/Medical/IV/IVDripOverlay.cs b/Content.Client/_RMC14/Medical/IV/IVDripOverlay.cs new file mode 100644 index 00000000000..0f81e2751a7 --- /dev/null +++ b/Content.Client/_RMC14/Medical/IV/IVDripOverlay.cs @@ -0,0 +1,72 @@ +using Content.Shared._RMC14.Medical.IV; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Map; + +namespace Content.Client._RMC14.Medical.IV; + +public sealed class IVDripOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entity = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities; + + public IVDripOverlay() + { + IoCManager.InjectDependencies(this); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var transformSystem = _entity.System(); + var handle = args.WorldHandle; + + var ivDrips = _entity.EntityQueryEnumerator(); + while (ivDrips.MoveNext(out var ivDripId, out var ivDripComponent)) + { + if (ivDripComponent.AttachedTo is not { Valid: true } attachedTo) + { + continue; + } + + var ivDripPosition = transformSystem.GetMapCoordinates(ivDripId); + var attachedPosition = transformSystem.GetMapCoordinates(attachedTo); + + if (ivDripPosition.MapId == MapId.Nullspace || attachedPosition.MapId == MapId.Nullspace) + continue; + + handle.DrawLine(ivDripPosition.Position, attachedPosition.Position, Color.White); + } + + var bloodPacks = _entity.EntityQueryEnumerator(); + while (bloodPacks.MoveNext(out var packId, out var packComponent)) + { + if (packComponent.AttachedTo is not { Valid: true } attachedTo) + continue; + + var packPosition = transformSystem.GetMapCoordinates(packId); + var attachedPosition = transformSystem.GetMapCoordinates(attachedTo); + + if (packPosition.MapId == MapId.Nullspace || attachedPosition.MapId == MapId.Nullspace) + continue; + + handle.DrawLine(packPosition.Position, attachedPosition.Position, Color.White); + } + + var dialysisMachines = _entity.EntityQueryEnumerator(); + while (dialysisMachines.MoveNext(out var dialysisId, out var dialysisComponent)) + { + if (dialysisComponent.AttachedTo is not { Valid: true } attachedTo) + continue; + + var dialysisPosition = transformSystem.GetMapCoordinates(dialysisId); + var attachedPosition = transformSystem.GetMapCoordinates(attachedTo); + + if (dialysisPosition.MapId == MapId.Nullspace || attachedPosition.MapId == MapId.Nullspace) + continue; + + handle.DrawLine(dialysisPosition.Position, attachedPosition.Position, Color.White); + } + } +} diff --git a/Content.Client/_RMC14/Medical/IV/IVDripSystem.cs b/Content.Client/_RMC14/Medical/IV/IVDripSystem.cs new file mode 100644 index 00000000000..b37417bfc3b --- /dev/null +++ b/Content.Client/_RMC14/Medical/IV/IVDripSystem.cs @@ -0,0 +1,113 @@ +using Content.Shared._RMC14.Medical.IV; +using Content.Shared.Rounding; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Timing; + +namespace Content.Client._RMC14.Medical.IV; + +public sealed class IVDripSystem : SharedIVDripSystem +{ + [Dependency] private readonly IOverlayManager _overlay = default!; + [Dependency] private readonly SpriteSystem _spriteSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + if (!_overlay.HasOverlay()) + _overlay.AddOverlay(new IVDripOverlay()); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlay.RemoveOverlay(); + } + + protected override void UpdateIVAppearance(Entity iv) + { + base.UpdateIVAppearance(iv); + if (!TryComp(iv, out SpriteComponent? sprite)) + return; + + var hookedState = iv.Comp.AttachedTo == default + ? iv.Comp.UnattachedState + : iv.Comp.AttachedState; + _spriteSystem.LayerSetRsiState((iv.Owner, sprite), IVDripVisualLayers.Base, hookedState); + + string? reagentState = null; + for (var i = iv.Comp.ReagentStates.Count - 1; i >= 0; i--) + { + var (amount, state) = iv.Comp.ReagentStates[i]; + if (amount <= iv.Comp.FillPercentage) + { + reagentState = state; + break; + } + } + + if (reagentState == null) + { + _spriteSystem.LayerSetVisible((iv.Owner, sprite), IVDripVisualLayers.Reagent, false); + return; + } + + _spriteSystem.LayerSetVisible((iv.Owner, sprite), IVDripVisualLayers.Reagent, true); + _spriteSystem.LayerSetRsiState((iv.Owner, sprite), IVDripVisualLayers.Reagent, reagentState); + _spriteSystem.LayerSetColor((iv.Owner, sprite), IVDripVisualLayers.Reagent, iv.Comp.FillColor); + } + + protected override void UpdatePackAppearance(Entity pack) + { + base.UpdatePackAppearance(pack); + if (!TryComp(pack, out SpriteComponent? sprite)) + return; + + // TODO RMC14 blood types + _spriteSystem.LayerSetVisible((pack.Owner, sprite), BloodPackVisuals.Label, false); + + if (_spriteSystem.LayerMapTryGet((pack.Owner, sprite), BloodPackVisuals.Fill, out var fillLayer, false)) + { + var fill = pack.Comp.FillPercentage.Float(); + var level = ContentHelpers.RoundToLevels(fill, 1, pack.Comp.MaxFillLevels + 1); + var state = level > 0 ? $"{pack.Comp.FillBaseName}{level}" : pack.Comp.FillBaseName; + _spriteSystem.LayerSetRsiState((pack.Owner, sprite), fillLayer, state); + _spriteSystem.LayerSetColor((pack.Owner, sprite), fillLayer, pack.Comp.FillColor); + _spriteSystem.LayerSetVisible((pack.Owner, sprite), fillLayer, true); + } + } + + protected override void UpdateDialysisAppearance(Entity dialysis) + { + base.UpdateDialysisAppearance(dialysis); + if (!TryComp(dialysis, out SpriteComponent? sprite)) + return; + + var attachmentState = dialysis.Comp.AttachedTo != null ? "hooked" : "unhooked"; + if (_spriteSystem.LayerMapTryGet((dialysis.Owner, sprite), DialysisVisualLayers.Attachment, out var attachmentLayer, false)) + _spriteSystem.LayerSetRsiState((dialysis.Owner, sprite), attachmentLayer, attachmentState); + + var isDetaching = dialysis.Comp.DetachingEnd > _timing.CurTime; + if (_spriteSystem.LayerMapTryGet((dialysis.Owner, sprite), DialysisVisualLayers.Effect, out var effectLayer, false)) + { + string? effectState = null; + if (isDetaching) + effectState = "draining"; + else if (dialysis.Comp.IsAttaching) + effectState = "filling"; + else if (dialysis.Comp.AttachedTo != null) + effectState = "running"; + + _spriteSystem.LayerSetVisible((dialysis.Owner, sprite), effectLayer, effectState != null); + if (effectState != null) + _spriteSystem.LayerSetRsiState((dialysis.Owner, sprite), effectLayer, effectState); + } + + if (_spriteSystem.LayerMapTryGet((dialysis.Owner, sprite), DialysisVisualLayers.Filtering, out var filteringLayer, false)) + { + var showFiltering = dialysis.Comp is { AttachedTo: not null, IsAttaching: false } && !isDetaching; + _spriteSystem.LayerSetVisible((dialysis.Owner, sprite), filteringLayer, showFiltering); + } + } +} diff --git a/Content.Server/_RMC14/Medical/IV/IVDripSystem.cs b/Content.Server/_RMC14/Medical/IV/IVDripSystem.cs new file mode 100644 index 00000000000..448886c1520 --- /dev/null +++ b/Content.Server/_RMC14/Medical/IV/IVDripSystem.cs @@ -0,0 +1,233 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Chat.Systems; +using Content.Shared.PowerCell.Components; +using Content.Shared._RMC14.Medical.IV; +using Content.Shared.Body.Components; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Damage; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Content.Shared.PowerCell; +using Content.Server.Body.Systems; +using Content.Server.Power.EntitySystems; + + +namespace Content.Server._RMC14.Medical.IV; + +public sealed class IVDripSystem : SharedIVDripSystem +{ + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; + [Dependency] private readonly BatterySystem _battery = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private readonly List> _reagentRemovalBuffer = []; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDialysisBatteryChargeChanged); + } + + private bool TryGetBloodstream( + EntityUid attachedTo, + [NotNullWhen(true)] out Entity? solEnt, + [NotNullWhen(true)] out Solution? solution, + out Entity? bloodstreamSolution) + { + solEnt = default; + solution = default; + bloodstreamSolution = default; + if (!TryComp(attachedTo, out BloodstreamComponent? attachedStream) || + !_solutionContainer.TryGetSolution(attachedTo, attachedStream.BloodSolutionName, out solEnt, out solution)) + { + return false; + } + + bloodstreamSolution = attachedStream.BloodSolution; + return true; + } + + protected override void DoRip(DamageSpecifier? damage, EntityUid attached, EntityUid? user, ProtoId ripEmote, bool predict) + { + base.DoRip(damage, attached, user, ripEmote, predict); + _chat.TryEmoteWithoutChat(attached, ripEmote); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var time = _timing.CurTime; + var ivs = EntityQueryEnumerator(); + while (ivs.MoveNext(out var ivId, out var ivComp)) + { + if (ivComp.AttachedTo is not { } attachedTo) + continue; + + if (!InRange(ivId, attachedTo, ivComp.Range)) + DetachIV((ivId, ivComp), null, true, false); + + if (time < ivComp.TransferAt) + continue; + + if (_itemSlots.GetItemOrNull(ivId, ivComp.Slot) is not { } pack) + continue; + + if (!TryComp(pack, out BloodPackComponent? packComponent)) + continue; + + ivComp.TransferAt = time + ivComp.TransferDelay; + + if (!_solutionContainer.TryGetSolution(pack, packComponent.Solution, out var packSolEnt, out var packSol)) + continue; + + if (!TryGetBloodstream(attachedTo, out var streamSolEnt, out var streamSol, out var attachedStream)) + continue; + + if (ivComp.Injecting) + { + if (attachedStream is { } bloodSolutionEnt && + bloodSolutionEnt.Comp.Solution.Volume < bloodSolutionEnt.Comp.Solution.MaxVolume) + { + _solutionContainer.TryTransferSolution(bloodSolutionEnt, packSol, ivComp.TransferAmount); + Dirty(packSolEnt.Value); + } + } + else + { + if (packSol.Volume < packSol.MaxVolume) + { + _solutionContainer.TryTransferSolution(packSolEnt.Value, streamSol, ivComp.TransferAmount); + Dirty(streamSolEnt.Value); + } + } + + Dirty(ivId, ivComp); + UpdateIVVisuals((ivId, ivComp)); + UpdatePackVisuals((pack, packComponent)); + } + + var packs = EntityQueryEnumerator(); + while (packs.MoveNext(out var packId, out var packComp)) + { + if (packComp.AttachedTo is not { } attachedTo) + continue; + + if (!InRange(packId, attachedTo, packComp.Range)) + DetachPack((packId, packComp), null, true, false); + + if (time < packComp.TransferAt) + continue; + + packComp.TransferAt = time + packComp.TransferDelay; + + if (!_solutionContainer.TryGetSolution(packId, packComp.Solution, out var packSolEnt, out var packSol)) + continue; + + if (!TryGetBloodstream(attachedTo, out var streamSolEnt, out var streamSol, out var attachedStream)) + continue; + + if (packComp.Injecting) + { + if (attachedStream is { } bloodSolutionEnt && + bloodSolutionEnt.Comp.Solution.Volume < bloodSolutionEnt.Comp.Solution.MaxVolume) + { + _solutionContainer.TryTransferSolution(bloodSolutionEnt, packSol, packComp.TransferAmount); + Dirty(packSolEnt.Value); + } + } + else + { + if (packSol.Volume < packSol.MaxVolume) + { + _solutionContainer.TryTransferSolution(packSolEnt.Value, streamSol, packComp.TransferAmount); + Dirty(streamSolEnt.Value); + } + } + + Dirty(packId, packComp); + UpdatePackVisuals((packId, packComp)); + } + + var dialysis = EntityQueryEnumerator(); + while (dialysis.MoveNext(out var dialysisId, out var dialysisComp)) + { + if (dialysisComp.DetachingEnd != TimeSpan.Zero && dialysisComp.DetachingEnd <= time) + { + dialysisComp.DetachingEnd = TimeSpan.Zero; + Dirty(dialysisId, dialysisComp); + } + + if (dialysisComp.AttachedTo is not { } attachedTo) + continue; + + if (!InRange(dialysisId, attachedTo, dialysisComp.Range)) + DetachDialysis((dialysisId, dialysisComp), null, true, false); + + if (!_powerCell.HasActivatableCharge(dialysisId)) + DetachDialysis((dialysisId, dialysisComp), null, false, false); + + if (time < dialysisComp.TransferAt) + continue; + + dialysisComp.TransferAt = time + dialysisComp.TransferDelay; + + if (!TryGetBloodstream(attachedTo, out var streamSolEnt, out _, out _)) + continue; + + if (_solutionContainer.TryGetSolution(attachedTo, null, out var chemSolEnt, out var chemSol)) + { + _reagentRemovalBuffer.Clear(); + foreach (var reagentQuantity in chemSol.Contents) + { + if (!dialysisComp.NonTransferableReagents.Contains(reagentQuantity.Reagent.Prototype)) + _reagentRemovalBuffer.Add(reagentQuantity.Reagent.Prototype); + } + + foreach (var reagent in _reagentRemovalBuffer) + { + if (chemSolEnt == null) + break; + + _solutionContainer.RemoveReagent(chemSolEnt.Value, reagent, dialysisComp.DialysisAmount); + } + } + + _solutionContainer.SplitSolution(streamSolEnt.Value, dialysisComp.BloodCost); + _powerCell.TryUseActivatableCharge(dialysisId); + Dirty(dialysisId, dialysisComp); + } + } + + private void OnDialysisBatteryChargeChanged(Entity dialysis, ref PowerCellChangedEvent args) + { + UpdateDialysisBatteryAppearance(dialysis.Owner, GetDialysisBatteryLevel(dialysis)); + } + + private DialysisBatteryLevel GetDialysisBatteryLevel(Entity dialysis) + { + if (!_powerCell.TryGetBatteryFromSlot(dialysis.Owner, out var battery) || battery == null) + return DialysisBatteryLevel.Battery0; + + var charge = _battery.GetCharge(dialysis.Owner); + return charge switch + { + > 0.85f => DialysisBatteryLevel.Battery100, + > 0.60f => DialysisBatteryLevel.Battery85, + > 0.45f => DialysisBatteryLevel.Battery60, + > 0.30f => DialysisBatteryLevel.Battery45, + > 0.15f => DialysisBatteryLevel.Battery30, + > 0.01f => DialysisBatteryLevel.Battery15, + _ => DialysisBatteryLevel.Battery0, + }; + } +} diff --git a/Content.Shared/_RMC14/Medical/IV/AttachBloodPackDoAfterEvent.cs b/Content.Shared/_RMC14/Medical/IV/AttachBloodPackDoAfterEvent.cs new file mode 100644 index 00000000000..7eb280d1590 --- /dev/null +++ b/Content.Shared/_RMC14/Medical/IV/AttachBloodPackDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._RMC14.Medical.IV; + +[Serializable, NetSerializable] +public sealed partial class AttachBloodPackDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_RMC14/Medical/IV/AttachDialysisDoAfterEvent.cs b/Content.Shared/_RMC14/Medical/IV/AttachDialysisDoAfterEvent.cs new file mode 100644 index 00000000000..a42ae70de91 --- /dev/null +++ b/Content.Shared/_RMC14/Medical/IV/AttachDialysisDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._RMC14.Medical.IV; + +[Serializable, NetSerializable] +public sealed partial class AttachDialysisDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_RMC14/Medical/IV/BloodPackComponent.cs b/Content.Shared/_RMC14/Medical/IV/BloodPackComponent.cs new file mode 100644 index 00000000000..23f1c04a2fe --- /dev/null +++ b/Content.Shared/_RMC14/Medical/IV/BloodPackComponent.cs @@ -0,0 +1,64 @@ +using Content.Shared.Chat.Prototypes; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared._RMC14.Medical.IV; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause] +public sealed partial class BloodPackComponent : Component +{ + [DataField, AutoNetworkedField] + public string Solution = "pack"; + + [DataField, AutoNetworkedField] + public FixedPoint2 FillPercentage; + + [DataField, AutoNetworkedField] + public Color FillColor; + + [DataField, AutoNetworkedField] + public int MaxFillLevels = 7; + + [DataField, AutoNetworkedField] + public string FillBaseName = "bloodpack"; + + [DataField, AutoNetworkedField] + public FixedPoint2 TransferAmount = FixedPoint2.New(3); + + [DataField, AutoNetworkedField] + public TimeSpan TransferDelay = TimeSpan.FromSeconds(2); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] + public TimeSpan TransferAt; + + [DataField, AutoNetworkedField] + public EntityUid? AttachedTo; + + [DataField, AutoNetworkedField] + public TimeSpan AttachDelay = TimeSpan.FromSeconds(1); + + [DataField, AutoNetworkedField] + public int Range = 2; + + [DataField] + public DamageSpecifier? RipDamage; + + [DataField, AutoNetworkedField] + public bool Injecting = true; + + [DataField, AutoNetworkedField] + public ProtoId RipEmote = "Scream"; + + // TODO RMC-14 blood types +} + +[Serializable, NetSerializable] +public enum BloodPackVisuals +{ + Label, + Fill +} diff --git a/Content.Shared/_RMC14/Medical/IV/IVDripComponent.cs b/Content.Shared/_RMC14/Medical/IV/IVDripComponent.cs new file mode 100644 index 00000000000..8139e9b6ad4 --- /dev/null +++ b/Content.Shared/_RMC14/Medical/IV/IVDripComponent.cs @@ -0,0 +1,68 @@ +using Content.Shared.Chat.Prototypes; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared._RMC14.Medical.IV; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause] +public sealed partial class IVDripComponent : Component +{ + [DataField, AutoNetworkedField] + public EntityUid? AttachedTo; + + [DataField, AutoNetworkedField] + public string Slot = "pack"; + + [DataField, AutoNetworkedField] + public FixedPoint2 TransferAmount = FixedPoint2.New(4.5); + + [DataField, AutoNetworkedField] + public TimeSpan TransferDelay = TimeSpan.FromSeconds(3); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] + public TimeSpan TransferAt; + + [DataField, AutoNetworkedField] + public string AttachedState = "hooked"; + + [DataField, AutoNetworkedField] + public string UnattachedState = "unhooked"; + + /// + /// Percentages are from 0 to 100 + /// + [DataField, AutoNetworkedField] + public List<(int Percentage, string State)> ReagentStates = new(); + + [DataField, AutoNetworkedField] + public Color FillColor; + + /// + /// From 0 to 100 + /// + [DataField, AutoNetworkedField] + public int FillPercentage; + + [DataField, AutoNetworkedField] + public int Range = 2; + + [DataField] + public DamageSpecifier? RipDamage; + + [DataField, AutoNetworkedField] + public bool Injecting = true; + + [DataField, AutoNetworkedField] + public ProtoId RipEmote = "Scream"; +} + +[Serializable, NetSerializable] +public enum IVDripVisualLayers +{ + Base, + Reagent +} diff --git a/Content.Shared/_RMC14/Medical/IV/IVDripTargetComponent.cs b/Content.Shared/_RMC14/Medical/IV/IVDripTargetComponent.cs new file mode 100644 index 00000000000..3b3108d347b --- /dev/null +++ b/Content.Shared/_RMC14/Medical/IV/IVDripTargetComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._RMC14.Medical.IV; + +[RegisterComponent, NetworkedComponent] +public sealed partial class IVDripTargetComponent : Component; diff --git a/Content.Shared/_RMC14/Medical/IV/PortableDialysisComponent.cs b/Content.Shared/_RMC14/Medical/IV/PortableDialysisComponent.cs new file mode 100644 index 00000000000..aacc3a83b85 --- /dev/null +++ b/Content.Shared/_RMC14/Medical/IV/PortableDialysisComponent.cs @@ -0,0 +1,77 @@ +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared._RMC14.Medical.IV; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause] +public sealed partial class PortableDialysisComponent : Component +{ + [DataField, AutoNetworkedField] + public FixedPoint2 DialysisAmount = FixedPoint2.New(1.5); + + [DataField, AutoNetworkedField] + public FixedPoint2 BloodCost = FixedPoint2.New(6); + + [DataField, AutoNetworkedField] + public TimeSpan TransferDelay = TimeSpan.FromSeconds(1); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] + public TimeSpan TransferAt; + + [DataField, AutoNetworkedField] + public EntityUid? AttachedTo; + + [DataField, AutoNetworkedField] + public TimeSpan AttachDelay = TimeSpan.FromSeconds(1.2); + + [DataField, AutoNetworkedField] + public int Range = 2; + + [DataField] + public DamageSpecifier? RipDamage; + + [DataField, AutoNetworkedField] + public ProtoId RipEmote = "Scream"; + + [DataField, AutoNetworkedField] + public ProtoId[] NonTransferableReagents = ["Blood"]; + + [DataField, AutoNetworkedField] + public bool IsAttaching; + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField] + public TimeSpan DetachingEnd; +} + +[Serializable, NetSerializable] +public enum DialysisVisualLayers +{ + Attachment, + Effect, + Filtering, + Battery +} + +[Serializable, NetSerializable] +public enum DialysisBatteryLevel : byte +{ + Battery0, + Battery15, + Battery30, + Battery45, + Battery60, + Battery85, + Battery100 +} + +[Serializable, NetSerializable] +public enum DialysisVisuals : byte +{ + BatteryLevel +} diff --git a/Content.Shared/_RMC14/Medical/IV/SharedIVDripSystem.cs b/Content.Shared/_RMC14/Medical/IV/SharedIVDripSystem.cs new file mode 100644 index 00000000000..a29e3b52d27 --- /dev/null +++ b/Content.Shared/_RMC14/Medical/IV/SharedIVDripSystem.cs @@ -0,0 +1,636 @@ +using System.Linq; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Damage; +using Content.Shared.Damage.Systems; +using Content.Shared.DoAfter; +using Content.Shared.DragDrop; +using Content.Shared.Examine; +using Content.Shared.FixedPoint; +using Content.Shared.Hands; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.PowerCell; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + + +namespace Content.Shared._RMC14.Medical.IV; + +public abstract class SharedIVDripSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private readonly HashSet _packsToUpdate = []; + + private EntityQuery _bloodPackQuery; + + public override void Initialize() + { + _bloodPackQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnIVDripEntInserted); + SubscribeLocalEvent(OnIVDripEntRemoved); + SubscribeLocalEvent(OnIVDripAfterHandleState); + SubscribeLocalEvent(OnIVDripCanDrag); + SubscribeLocalEvent(OnIVDripCanDropDragged); + SubscribeLocalEvent(OnIVDripDragDropDragged); + SubscribeLocalEvent(OnIVInteractHand); + SubscribeLocalEvent>(OnIVVerbs); + SubscribeLocalEvent(OnIVExamine); + + SubscribeLocalEvent(OnIVTargetCanDropTarget); + + SubscribeLocalEvent(OnBloodPackMapInit); + SubscribeLocalEvent(OnBloodPackAfterState); + SubscribeLocalEvent(OnBloodPackSolutionChanged); + SubscribeLocalEvent(OnBloodPackAfterInteract); + SubscribeLocalEvent(OnBloodPackAttachDoAfter); + SubscribeLocalEvent(OnBloodPackUnequippedHand); + SubscribeLocalEvent>(OnBloodPackVerbs); + SubscribeLocalEvent(OnBloodPackExamine); + + SubscribeLocalEvent(OnDialysisAfterInteract); + SubscribeLocalEvent(OnDialysisDoAfter); + SubscribeLocalEvent(OnDialysisUnequippedHand); + SubscribeLocalEvent(OnDialysisExamine); + SubscribeLocalEvent(OnDialysisAfterHandleState); + SubscribeLocalEvent(OnDialysisPowerEmpty); + } + + private void OnIVDripEntInserted(Entity iv, ref EntInsertedIntoContainerMessage args) + { + UpdateIVVisuals(iv); + } + + private void OnIVDripEntRemoved(Entity iv, ref EntRemovedFromContainerMessage args) + { + UpdateIVVisuals(iv); + } + + private void OnIVDripAfterHandleState(Entity iv, ref AfterAutoHandleStateEvent args) + { + UpdateIVAppearance(iv); + } + + private void OnIVDripCanDrag(Entity iv, ref CanDragEvent args) + { + args.Handled = true; + } + + private void OnIVDripCanDropDragged(Entity iv, ref CanDropDraggedEvent args) + { + if (!HasComp(args.Target) || !InRange(iv, args.Target, iv.Comp.Range)) + return; + args.Handled = true; + args.CanDrop = true; + } + + private void OnIVTargetCanDropTarget(Entity marine, ref CanDropTargetEvent args) + { + var iv = args.Dragged; + if (!TryComp(iv, out IVDripComponent? ivComp) || !InRange(iv, marine, ivComp.Range)) + return; + args.Handled = true; + args.CanDrop = true; + } + + private void OnIVDripDragDropDragged(Entity iv, ref DragDropDraggedEvent args) + { + if (args.Handled) + return; + + if (iv.Comp.AttachedTo == default) + AttachIV(iv, args.User, args.Target); + else + DetachIV(iv, args.User, false, true); + } + + private void OnIVInteractHand(Entity iv, ref InteractHandEvent args) + { + DetachIV(iv, args.User, false, true); + } + + private void OnIVVerbs(Entity iv, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var user = args.User; + args.Verbs.Add(new InteractionVerb + { + Act = () => ToggleInject(iv, user), + Text = Loc.GetString("cm-iv-verb-toggle-inject"), + }); + } + + private void OnIVExamine(Entity ent, ref ExaminedEvent args) + { + using (args.PushGroup(nameof(IVDripComponent))) + { + var injectingMsg = ent.Comp.Injecting + ? "cm-iv-examine-injecting" + : "cm-iv-examine-drawing"; + args.PushMarkup(Loc.GetString(injectingMsg, ("iv", ent.Owner))); + + var chemicalsMsg = Loc.GetString("cm-iv-examine-chemicals-none"); + if (_containers.TryGetContainer(ent, ent.Comp.Slot, out var container) && + container.ContainedEntities.FirstOrDefault() is { Valid: true } packId && + TryComp(packId, out BloodPackComponent? pack) && + _solutionContainer.TryGetSolution(packId, pack.Solution, out _, out var solution)) + { + chemicalsMsg = Loc.GetString("cm-iv-examine-chemicals", + ("attached", packId), + ("units", solution.Volume.Int())); + } + + args.PushMarkup(chemicalsMsg); + + var attachedMsg = ent.Comp.AttachedTo is { } attached + ? Loc.GetString("cm-iv-examine-attached", ("attached", attached)) + : Loc.GetString("cm-iv-examine-attached-none"); + args.PushMarkup(attachedMsg); + } + } + + private void OnBloodPackMapInit(Entity pack, ref MapInitEvent args) + { + _packsToUpdate.Add(pack); + } + + private void OnBloodPackAfterState(Entity pack, ref AfterAutoHandleStateEvent args) + { + UpdatePackVisuals(pack); + } + + private void OnBloodPackSolutionChanged(Entity pack, ref SolutionContainerChangedEvent args) + { + UpdatePackVisuals(pack); + } + + private void OnBloodPackAfterInteract(Entity pack, ref AfterInteractEvent args) + { + if (args.Target is not { } target) + return; + + if (!InRange(pack, target, pack.Comp.Range) || !HasComp(target)) + return; + + args.Handled = true; + + var user = args.User; + if (pack.Comp.AttachedTo != null) + { + DetachPack((pack, pack), user, false, true); + return; + } + + if (user == target) + { + _popup.PopupClient(Loc.GetString("cm-blood-pack-cannot-self"), user, user); + return; + } + + var delay = pack.Comp.AttachDelay; + if (delay > TimeSpan.Zero) + { + var selfPoke = Loc.GetString("cm-blood-pack-poke-self", ("pack", pack.Owner), ("target", target)); + var othersPoke = Loc.GetString("cm-blood-pack-poke-others", + ("user", user), + ("pack", pack.Owner), + ("target", target)); + _popup.PopupPredicted(selfPoke, othersPoke, target, user); + } + + var ev = new AttachBloodPackDoAfterEvent(); + var doAfter = new DoAfterArgs(EntityManager, user, delay, ev, pack, target, pack) + { + BreakOnMove = true, + BreakOnDamage = true, + BreakOnHandChange = true, + BlockDuplicate = true, + DuplicateCondition = DuplicateConditions.SameEvent, + }; + _doAfter.TryStartDoAfter(doAfter); + } + + private void OnBloodPackAttachDoAfter(Entity pack, ref AttachBloodPackDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Target is not { } target) + return; + + AttachPack(pack, args.User, target); + } + + private void OnBloodPackUnequippedHand(Entity pack, ref GotUnequippedHandEvent args) + { + DetachPack((pack, pack), args.User, true, true); + } + + private void OnBloodPackVerbs(Entity pack, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var user = args.User; + args.Verbs.Add(new InteractionVerb + { + Act = () => ToggleInject(pack, user), + Text = Loc.GetString("cm-iv-verb-toggle-inject"), + }); + } + + private void OnBloodPackExamine(Entity pack, ref ExaminedEvent args) + { + using (args.PushGroup(nameof(BloodPackComponent))) + { + var injectingMsg = pack.Comp.Injecting + ? "cm-iv-examine-injecting" + : "cm-iv-examine-drawing"; + args.PushMarkup(Loc.GetString(injectingMsg, ("iv", pack.Owner))); + + var attachedMsg = pack.Comp.AttachedTo is { } attached + ? Loc.GetString("cm-iv-examine-attached", ("attached", attached)) + : Loc.GetString("cm-iv-examine-attached-none"); + args.PushMarkup(attachedMsg); + + if (_solutionContainer.TryGetSolution(pack.Owner, pack.Comp.Solution, out _, out var solution)) + args.PushMarkup(Loc.GetString("cm-blood-pack-contains", ("units", solution.Volume.Int()))); + } + } + + private void OnDialysisAfterInteract(Entity dialysis, ref AfterInteractEvent args) + { + if (args.Target is not { } target) + return; + + if (!InRange(dialysis, target, dialysis.Comp.Range) || !HasComp(target)) + return; + + args.Handled = true; + + var user = args.User; + if (dialysis.Comp.AttachedTo != null) + { + DetachDialysis(dialysis, args.User, false, true); + return; + } + + if (user == target) + { + _popup.PopupClient(Loc.GetString("cm-blood-pack-cannot-self"), user, user); + return; + } + + var delay = dialysis.Comp.AttachDelay; + if (delay > TimeSpan.Zero) + { + var selfPoke = Loc.GetString("cm-blood-pack-poke-self", ("pack", dialysis.Owner), ("target", target)); + var othersPoke = Loc.GetString("cm-blood-pack-poke-others", + ("user", user), + ("pack", dialysis.Owner), + ("target", target)); + _popup.PopupPredicted(selfPoke, othersPoke, target, user); + } + + dialysis.Comp.IsAttaching = true; + Dirty(dialysis); + + var ev = new AttachDialysisDoAfterEvent(); + var doAfter = new DoAfterArgs(EntityManager, user, delay, ev, dialysis, target, dialysis) + { + BreakOnMove = true, + BreakOnDamage = true, + BreakOnHandChange = true, + BlockDuplicate = true, + DuplicateCondition = DuplicateConditions.SameEvent, + }; + _doAfter.TryStartDoAfter(doAfter); + } + + private void OnDialysisDoAfter(Entity dialysis, ref AttachDialysisDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Target is not { } target) + { + dialysis.Comp.IsAttaching = false; + dialysis.Comp.DetachingEnd = TimeSpan.Zero; + Dirty(dialysis); + return; + } + + dialysis.Comp.IsAttaching = false; + AttachDialysis(dialysis, args.User, target); + } + + private void OnDialysisUnequippedHand(Entity dialysis, ref GotUnequippedHandEvent args) + { + DetachDialysis((dialysis, dialysis), args.User, true, true); + } + + private void OnDialysisExamine(Entity dialysis, ref ExaminedEvent args) + { + using (args.PushGroup(nameof(PortableDialysisComponent))) + { + var attachedMsg = dialysis.Comp.AttachedTo is { } attached + ? Loc.GetString("cm-iv-examine-attached", ("attached", attached)) + : Loc.GetString("cm-iv-examine-attached-none"); + args.PushMarkup(attachedMsg); + } + } + + private void OnDialysisAfterHandleState(Entity dialysis, ref AfterAutoHandleStateEvent args) + { + UpdateDialysisAppearance(dialysis); + } + + private void OnDialysisPowerEmpty(Entity dialysis, ref PowerCellSlotEmptyEvent args) + { + DetachDialysis(dialysis, null, true, true); + } + + protected bool InRange(EntityUid iv, EntityUid to, float range) + { + var ivPos = _transform.GetMapCoordinates(iv); + var toPos = _transform.GetMapCoordinates(to); + return ivPos.InRange(toPos, range); + } + + private void AttachIV(Entity iv, EntityUid user, EntityUid to) + { + if (!InRange(iv, to, iv.Comp.Range)) + return; + + iv.Comp.AttachedTo = to; + Dirty(iv); + + AttachFeedback(iv, user, to, iv.Comp.Injecting); + } + + protected void DetachIV(Entity iv, EntityUid? user, bool rip, bool predict) + { + if (iv.Comp.AttachedTo is not { } target) + return; + + iv.Comp.AttachedTo = default; + Dirty(iv); + + if (rip) + DoRip(iv.Comp.RipDamage, target, user, iv.Comp.RipEmote, predict); + else + DoDetachFeedback(iv, target, user, predict); + } + + private void AttachPack(Entity pack, EntityUid user, EntityUid to) + { + if (!InRange(pack, to, pack.Comp.Range)) + return; + + pack.Comp.AttachedTo = to; + Dirty(pack); + + AttachFeedback(pack, user, to, pack.Comp.Injecting); + } + + protected void DetachPack(Entity pack, EntityUid? user, bool rip, bool predict) + { + if (pack.Comp.AttachedTo is not { } target) + return; + + pack.Comp.AttachedTo = default; + Dirty(pack); + + if (rip) + DoRip(pack.Comp.RipDamage, target, user, pack.Comp.RipEmote, predict); + else + DoDetachFeedback(pack, target, user, predict); + } + + private void AttachDialysis(Entity dialysis, EntityUid user, EntityUid to) + { + if (!InRange(dialysis, to, dialysis.Comp.Range)) + return; + + dialysis.Comp.DetachingEnd = TimeSpan.Zero; + dialysis.Comp.IsAttaching = false; + dialysis.Comp.AttachedTo = to; + Dirty(dialysis); + + _powerCell.SetDrawEnabled((dialysis.Owner, null), true); + + AttachFeedback(dialysis, user, to, false); + } + + protected void DetachDialysis(Entity dialysis, EntityUid? user, bool rip, bool predict) + { + if (dialysis.Comp.AttachedTo is not { } target) + return; + + dialysis.Comp.AttachedTo = default; + dialysis.Comp.DetachingEnd = _timing.CurTime + dialysis.Comp.AttachDelay; + Dirty(dialysis); + + _powerCell.SetDrawEnabled((dialysis.Owner, null), false); + + if (rip) + DoRip(dialysis.Comp.RipDamage, target, user, dialysis.Comp.RipEmote, predict); + else + DoDetachFeedback(dialysis, target, user, predict); + } + + private void ToggleInject(Entity iv, EntityUid user) + { + ToggleInject(iv, ref iv.Comp.Injecting, user); + Dirty(iv); + } + + private void ToggleInject(Entity pack, EntityUid user) + { + ToggleInject(pack, ref pack.Comp.Injecting, user); + Dirty(pack); + } + + private void ToggleInject(EntityUid iv, ref bool injecting, EntityUid user) + { + injecting = !injecting; + + var msg = injecting + ? Loc.GetString("cm-iv-now-injecting") + : Loc.GetString("cm-iv-now-taking"); + + _popup.PopupClient(msg, iv, user); + } + + protected void UpdatePackVisuals(Entity pack) + { + if (!_solutionContainer.TryGetSolution(pack.Owner, pack.Comp.Solution, out _, out var solution)) + { + UpdatePackAppearance(pack); + return; + } + + if (_containers.TryGetContainingContainer((pack, null), out var container) && + TryComp(container.Owner, out IVDripComponent? iv)) + { + iv.FillColor = solution.GetColor(_prototype); + iv.FillPercentage = (int) (solution.Volume / solution.MaxVolume * 100); + Dirty(container.Owner, iv); + UpdateIVAppearance((container.Owner, iv)); + } + + UpdatePackAppearance(pack); + } + + protected void UpdateIVVisuals(Entity iv) + { + // the client doesn't always know about solutions + if (_net.IsClient) + { + UpdateIVAppearance(iv); + return; + } + + if (!_containers.TryGetContainer(iv, iv.Comp.Slot, out var container)) + return; + + foreach (var entity in container.ContainedEntities) + { + if (!TryComp(entity, out BloodPackComponent? pack) || + !_solutionContainer.TryGetSolution(entity, pack.Solution, out _, out var solution)) + continue; + + iv.Comp.FillColor = solution.GetColor(_prototype); + iv.Comp.FillPercentage = (int) (solution.Volume / solution.MaxVolume * 100); + Dirty(iv); + UpdateIVAppearance(iv); + return; + } + + iv.Comp.FillColor = Color.White; + iv.Comp.FillPercentage = 0; + Dirty(iv); + UpdateIVAppearance(iv); + } + + protected virtual void UpdateIVAppearance(Entity iv) + { + } + + protected virtual void UpdatePackAppearance(Entity pack) + { + if (_net.IsClient) + return; + + if (_solutionContainer.TryGetSolution(pack.Owner, pack.Comp.Solution, out var solEnt)) + { + var solution = solEnt.Value.Comp.Solution; + pack.Comp.FillPercentage = solution.Volume / solution.MaxVolume; + pack.Comp.FillColor = solution.GetColor(_prototype); + } + else + { + pack.Comp.FillPercentage = FixedPoint2.Zero; + pack.Comp.FillColor = Color.Transparent; + } + + Dirty(pack); + } + + protected virtual void UpdateDialysisAppearance(Entity dialysis) + { + } + + protected void UpdateDialysisBatteryAppearance(Entity ent, DialysisBatteryLevel batteryLevel) + { + _appearance.SetData(ent, DialysisVisuals.BatteryLevel, batteryLevel, ent.Comp); + } + + protected virtual void DoRip(DamageSpecifier? damage, + EntityUid attached, + EntityUid? user, + ProtoId ripEmote, + bool predict) + { + if (damage != null) + _damageable.TryChangeDamage(attached, damage, true); + + if (!_timing.IsFirstTimePredicted) + return; + + var message = Loc.GetString("cm-iv-rip", ("target", attached)); + if (predict) + { + _popup.PopupClient(message, attached, user); + + var others = user == null ? Filter.Pvs(attached) : Filter.PvsExcept(user.Value); + _popup.PopupEntity(message, attached, others, true); + } + else + { + _popup.PopupEntity(message, attached); + } + } + + private void AttachFeedback(EntityUid iv, EntityUid user, EntityUid to, bool injecting) + { + if (!_timing.IsFirstTimePredicted) + return; + + var selfMessage = "cm-iv-attach-self-drawing"; + var othersMessage = "cm-iv-attach-others-drawing"; + if (injecting) + { + selfMessage = "cm-iv-attach-self-injecting"; + othersMessage = "cm-iv-attach-others-injecting"; + } + + _popup.PopupClient(Loc.GetString(selfMessage, ("iv", iv), ("target", to)), to, user); + + var others = Filter.PvsExcept(user); + _popup.PopupEntity(Loc.GetString(othersMessage, ("iv", iv), ("user", user), ("target", to)), to, others, true); + } + + private void DoDetachFeedback(EntityUid iv, EntityUid attached, EntityUid? user, bool predict) + { + var selfMessage = Loc.GetString("cm-iv-detach-self", ("iv", iv), ("target", attached)); + if (predict) + _popup.PopupClient(selfMessage, attached, user); + else + _popup.PopupEntity(selfMessage, attached); + + if (user == null) + return; + + var others = Filter.PvsExcept(user.Value); + _popup.PopupEntity(Loc.GetString("cm-iv-detach-others", ("iv", iv), ("user", user), ("target", attached)), + attached, + others, + true); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var pack in _packsToUpdate) + { + if (_bloodPackQuery.TryComp(pack, out var comp)) + UpdatePackVisuals((pack, comp)); + } + + _packsToUpdate.Clear(); + } +} diff --git a/Resources/Prototypes/Body/species_base.yml b/Resources/Prototypes/Body/species_base.yml index 0fa59a5e840..61014ea2181 100644 --- a/Resources/Prototypes/Body/species_base.yml +++ b/Resources/Prototypes/Body/species_base.yml @@ -203,6 +203,7 @@ save: false components: - type: EquipmentContainer # Lust edit - контейнер запираемой экипировки + - type: IVDripTarget # Lust edit - капельница - type: Hunger - type: Thirst - type: Barotrauma diff --git a/Resources/Prototypes/_RMC14/Entities/Medical/iv.yml b/Resources/Prototypes/_RMC14/Entities/Medical/iv.yml new file mode 100644 index 00000000000..f7cb7ec3d0a --- /dev/null +++ b/Resources/Prototypes/_RMC14/Entities/Medical/iv.yml @@ -0,0 +1,116 @@ +- type: entity + id: CMIV + name: iv + description: Allows you to inject blood into a patient or extract blood from them to do a blood transfusion. + suffix: IV, empty + components: + - type: Physics + bodyType: Dynamic + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.45,-0.45,0.45,0.45" + density: 60 + mask: + - MobMask + - type: Transform + anchored: false + noRot: true + - type: InteractionOutline + - type: Clickable + - type: Sprite + noRot: true + sprite: _RMC14/Structures/Machines/Medical/iv_drip.rsi + layers: + - state: unhooked + map: [ "enum.IVDripVisualLayers.Base" ] + - state: reagent0 + map: [ "enum.IVDripVisualLayers.Reagent" ] + - type: IVDrip + reagentStates: + - 0: reagent0 + - 10: reagent10 + - 25: reagent25 + - 50: reagent50 + - 75: reagent75 + - 80: reagent80 + - 100: reagent100 + ripDamage: + types: + Piercing: 3 + - type: ItemSlots + slots: + pack: + name: pack + whitelist: + components: + - BloodPack + - type: ContainerContainer + containers: + pack: !type:ContainerSlot {} + +- type: entity + parent: BaseItem + id: CMBloodPack # TODO RMC14 blood types + name: blood pack + description: A blood pack. Contains fluids, typically used for transfusions. + suffix: CM, empty + components: + - type: BloodPack + - type: Sprite + sprite: _RMC14/Objects/Medical/blood_pack.rsi + layers: + - state: bloodpack1 + map: ["enum.BloodPackVisuals.Fill"] + visible: false + - state: bloodpack + - state: blank + map: ["enum.BloodPackVisuals.Label"] + - type: Item + size: Small + sprite: _RMC14/Objects/Medical/blood_pack.rsi + - type: SolutionContainerManager + solutions: + pack: + maxVol: 300 + - type: RefillableSolution + solution: pack + - type: DrainableSolution + solution: pack + - type: SolutionTransfer + canChangeTransferAmount: true + - type: Appearance + - type: DrawableSolution + solution: pack + - type: InjectableSolution + solution: pack + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Glass + - type: DamageOnHighSpeedImpact + minimumSpeed: 2 + damage: + types: + Blunt: 5 + - type: StaticPrice + price: 30 + - type: Clothing + quickEquip: false + slots: + - suitstorage + +- type: entity + parent: CMBloodPack + id: CMBloodPackFull # TODO RMC14 blood types + name: blood pack + suffix: CM, Full + components: + - type: SolutionContainerManager + solutions: + pack: + maxVol: 300 + reagents: + - ReagentId: Blood + Quantity: 300 diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/a+.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/a+.png new file mode 100644 index 00000000000..44240f5b36c Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/a+.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/a-.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/a-.png new file mode 100644 index 00000000000..993879b226e Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/a-.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/ab+.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/ab+.png new file mode 100644 index 00000000000..bdb8c0a2fc9 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/ab+.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/ab-.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/ab-.png new file mode 100644 index 00000000000..639c2f5a964 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/ab-.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/b+.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/b+.png new file mode 100644 index 00000000000..5e9f87653d2 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/b+.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/b-.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/b-.png new file mode 100644 index 00000000000..71534765b59 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/b-.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/blank.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/blank.png new file mode 100644 index 00000000000..4b1ed1cf045 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/blank.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack.png new file mode 100644 index 00000000000..697d806f346 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack1.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack1.png new file mode 100644 index 00000000000..6f89665df53 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack1.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack2.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack2.png new file mode 100644 index 00000000000..192a50b9c01 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack2.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack3.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack3.png new file mode 100644 index 00000000000..0702c33973e Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack3.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack4.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack4.png new file mode 100644 index 00000000000..61398346a26 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack4.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack5.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack5.png new file mode 100644 index 00000000000..5b7ec252d47 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack5.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack6.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack6.png new file mode 100644 index 00000000000..22e9ecc0420 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack6.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack7.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack7.png new file mode 100644 index 00000000000..9ba825343e4 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/bloodpack7.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/meta.json b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/meta.json new file mode 100644 index 00000000000..ea0072a2a32 --- /dev/null +++ b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/meta.json @@ -0,0 +1,65 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/1d28964d37f9b95773580cca3471a2a4f5c03eb0/icons/obj/items/bloodpack.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "bloodpack" + }, + { + "name": "blank" + }, + { + "name": "x" + }, + { + "name": "o\u002B" + }, + { + "name": "o-" + }, + { + "name": "ab\u002B" + }, + { + "name": "ab-" + }, + { + "name": "b\u002B" + }, + { + "name": "b-" + }, + { + "name": "a\u002B" + }, + { + "name": "a-" + }, + { + "name": "bloodpack1" + }, + { + "name": "bloodpack2" + }, + { + "name": "bloodpack3" + }, + { + "name": "bloodpack4" + }, + { + "name": "bloodpack5" + }, + { + "name": "bloodpack6" + }, + { + "name": "bloodpack7" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/o+.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/o+.png new file mode 100644 index 00000000000..9d5b5991663 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/o+.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/o-.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/o-.png new file mode 100644 index 00000000000..2891be39401 Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/o-.png differ diff --git a/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/x.png b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/x.png new file mode 100644 index 00000000000..24b88149d5f Binary files /dev/null and b/Resources/Textures/_RMC14/Objects/Medical/blood_pack.rsi/x.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/beakerlarge100.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/beakerlarge100.png new file mode 100644 index 00000000000..76f310cb574 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/beakerlarge100.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/hooked.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/hooked.png new file mode 100644 index 00000000000..04e74b804ea Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/hooked.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/meta.json b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/meta.json new file mode 100644 index 00000000000..48bbb4a12d8 --- /dev/null +++ b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/meta.json @@ -0,0 +1,41 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/f7b9367cb5c083a8c052a40dece9417031e28ffe/icons/obj/structures/machinery/iv_drip.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "hooked" + }, + { + "name": "beakerlarge100" + }, + { + "name": "unhooked" + }, + { + "name": "reagent100" + }, + { + "name": "reagent80" + }, + { + "name": "reagent75" + }, + { + "name": "reagent50" + }, + { + "name": "reagent25" + }, + { + "name": "reagent10" + }, + { + "name": "reagent0" + } + ] +} diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent0.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent0.png new file mode 100644 index 00000000000..4c59d3a28cc Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent0.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent10.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent10.png new file mode 100644 index 00000000000..791621ed6cf Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent10.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent100.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent100.png new file mode 100644 index 00000000000..2dc6f230a4e Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent100.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent25.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent25.png new file mode 100644 index 00000000000..6f9ba32f0a7 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent25.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent50.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent50.png new file mode 100644 index 00000000000..7854b5b3a6e Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent50.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent75.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent75.png new file mode 100644 index 00000000000..25a3abe2376 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent75.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent80.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent80.png new file mode 100644 index 00000000000..25a3abe2376 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/reagent80.png differ diff --git a/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/unhooked.png b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/unhooked.png new file mode 100644 index 00000000000..72a3552ed70 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/Machines/Medical/iv_drip.rsi/unhooked.png differ