From f81226167115f33cb9c9f292f243af33e573ccf2 Mon Sep 17 00:00:00 2001 From: Dirius Date: Wed, 3 Jun 2026 18:06:43 -0400 Subject: [PATCH 1/3] Add privacy holoprojectors --- .../LabelableHolosignProjectorSystem.cs | 21 ++ ...abelableHolosignProjectorDescriptionBUI.cs | 46 ++++ .../UI/LabelableHolosignProjectorSignBUI.cs | 71 ++++++ .../UI/LabelableHolosignProjectorWindow.xaml | 23 ++ .../LabelableHolosignProjectorWindow.xaml.cs | 51 +++++ .../LabelableHolosignProjectorSystem.cs | 9 + .../Climbing/Components/ClimbableComponent.cs | 6 + Content.Shared/Clumsy/ClumsySystem.cs | 4 + .../LabelableHolosignProjectorComponent.cs | 38 ++++ .../Components/LabeledHolosignComponent.cs | 15 ++ .../Events/LabelableHolosignEvents.cs | 26 +++ .../SharedLabelableHolosignProjectorSystem.cs | 203 ++++++++++++++++++ .../_DEN/Whitelist/HolobarrierComponent.cs | 9 + .../_DEN/holoprojector/labelable_holosign.ftl | 9 + .../Entities/Structures/Machines/lathe.yml | 1 + Resources/Prototypes/_DEN/Consent/consent.yml | 5 +- .../Objects/Devices/holoprojectors.yml | 50 +++++ .../Structures/Holographic/projections.yml | 86 ++++++++ .../_DEN/Recipes/Lathes/Packs/shared.yml | 4 + .../_DEN/Recipes/Lathes/devices.yml | 7 + Resources/Prototypes/_DEN/tags.yml | 3 + .../private.rsi/icon-inhand-left.png | Bin 0 -> 1967 bytes .../private.rsi/icon-inhand-right.png | Bin 0 -> 1905 bytes .../Holoprojectors/private.rsi/icon.png | Bin 0 -> 1169 bytes .../Holoprojectors/private.rsi/meta.json | 82 +++++++ .../_DEN/Structures/Holo/adult.rsi/icon.png | Bin 0 -> 1487 bytes .../_DEN/Structures/Holo/adult.rsi/meta.json | 32 +++ .../_DEN/Structures/Holo/private.rsi/icon.png | Bin 0 -> 2954 bytes .../Structures/Holo/private.rsi/meta.json | 32 +++ 29 files changed, 832 insertions(+), 1 deletion(-) create mode 100644 Content.Client/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs create mode 100644 Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorDescriptionBUI.cs create mode 100644 Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorSignBUI.cs create mode 100644 Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml create mode 100644 Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml.cs create mode 100644 Content.Server/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs create mode 100644 Content.Shared/_DEN/Holosign/Components/LabelableHolosignProjectorComponent.cs create mode 100644 Content.Shared/_DEN/Holosign/Components/LabeledHolosignComponent.cs create mode 100644 Content.Shared/_DEN/Holosign/Events/LabelableHolosignEvents.cs create mode 100644 Content.Shared/_DEN/Holosign/Systems/SharedLabelableHolosignProjectorSystem.cs create mode 100644 Content.Shared/_DEN/Whitelist/HolobarrierComponent.cs create mode 100644 Resources/Locale/en-US/_DEN/holoprojector/labelable_holosign.ftl create mode 100644 Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml create mode 100644 Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml create mode 100644 Resources/Prototypes/_DEN/Recipes/Lathes/Packs/shared.yml create mode 100644 Resources/Prototypes/_DEN/Recipes/Lathes/devices.yml create mode 100644 Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/icon-inhand-left.png create mode 100644 Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/icon-inhand-right.png create mode 100644 Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/icon.png create mode 100644 Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/meta.json create mode 100644 Resources/Textures/_DEN/Structures/Holo/adult.rsi/icon.png create mode 100644 Resources/Textures/_DEN/Structures/Holo/adult.rsi/meta.json create mode 100644 Resources/Textures/_DEN/Structures/Holo/private.rsi/icon.png create mode 100644 Resources/Textures/_DEN/Structures/Holo/private.rsi/meta.json diff --git a/Content.Client/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs b/Content.Client/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs new file mode 100644 index 00000000000..974f1c99da0 --- /dev/null +++ b/Content.Client/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs @@ -0,0 +1,21 @@ +using Content.Client._DEN.Holosign.UI; +using Content.Shared._DEN.Holosign.Components; +using Content.Shared._DEN.Holosign.Events; +using Content.Shared._DEN.Holosign.Systems; +using Robust.Client.GameObjects; + + +namespace Content.Client._DEN.Holosign.Systems; + + +public sealed partial class LabelableHolosignProjectorSystem : SharedLabelableHolosignProjectorSystem +{ + protected override void UpdateUI(Entity ent) + { + if (_uiSystem.TryGetOpenUi(ent.Owner, LabelableHolosignUIKey.Description, out var bui) + && bui is LabelableHolosignProjectorDescriptionBUI cBui) + { + cBui.Reload(); + } + } +} diff --git a/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorDescriptionBUI.cs b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorDescriptionBUI.cs new file mode 100644 index 00000000000..1a1c15cf4ef --- /dev/null +++ b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorDescriptionBUI.cs @@ -0,0 +1,46 @@ +using Content.Shared._DEN.Holosign.Components; +using Content.Shared._DEN.Holosign.Events; +using JetBrains.Annotations; +using Robust.Client.UserInterface; + +namespace Content.Client._DEN.Holosign.UI; + +[UsedImplicitly] +public sealed partial class LabelableHolosignProjectorDescriptionBUI : BoundUserInterface +{ + [Dependency] private IEntityManager _entManager = default!; + + [ViewVariables] + private LabelableHolosignProjectorWindow? _window; + + public LabelableHolosignProjectorDescriptionBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + _window = this.CreateWindow(); + _window.OnDescriptionChanged += OnDescriptionChanged; + Reload(); + } + + private void OnDescriptionChanged(string description, bool isNsfw) + { + if (_entManager.TryGetComponent(Owner, out LabelableHolosignProjectorComponent? projector) && + projector.BarrierDescription.Equals(description) && projector.IsNsfw == isNsfw) + return; + + SendPredictedMessage(new LabelableHolosignDescriptionMessage(description, isNsfw)); + } + + public void Reload() + { + if (_window == null || !_entManager.TryGetComponent(Owner, out LabelableHolosignProjectorComponent? projector)) + return; + + _window.SetCurrentDescription(projector.BarrierDescription); + _window.SetCurrentNsfw(projector.IsNsfw); + } +} diff --git a/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorSignBUI.cs b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorSignBUI.cs new file mode 100644 index 00000000000..a7a3158380f --- /dev/null +++ b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorSignBUI.cs @@ -0,0 +1,71 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared._DEN.Holosign.Components; +using Content.Shared._DEN.Holosign.Events; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Client._DEN.Holosign.UI; + +[UsedImplicitly] +public sealed partial class LabelableHolosignProjectorSignBUI : BoundUserInterface +{ + [Dependency] private IEntityManager _entManager = default!; + [Dependency] private IPrototypeManager _protoManager = default!; + + [ViewVariables] private SimpleRadialMenu? _menu; + + public LabelableHolosignProjectorSignBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _menu = this.CreateWindow(); + _menu.Track(Owner); + + var controls = new List(); + controls.Add(new RadialMenuActionOption(OpenDescriptionWindow, LabelableHolosignUIKey.Description) + { + IconSpecifier = RadialMenuIconSpecifier.With(new SpriteSpecifier.Texture(new("/Textures/Interface/examine-star.png"))), + ToolTip = Loc.GetString("labelable-holoprojector-ui-set-description") + }); + + if (_entManager.TryGetComponent(Owner, out LabelableHolosignProjectorComponent? projector)) + { + foreach (var proto in projector.SignProtos) + { + if (_protoManager.TryIndex(proto, out var entProto)) + { + controls.Add( + new RadialMenuActionOption(SelectSignProto, proto) + { + IconSpecifier = RadialMenuIconSpecifier.With(entProto), + ToolTip = Loc.GetString(entProto.Name), + }); + } + } + } + _menu.SetButtons(controls); + } + + private void SelectSignProto(EntProtoId protoId) + { + if (!_entManager.TryGetComponent(Owner, out LabelableHolosignProjectorComponent? projector)) + return; + + var selected = projector.SignProtos.IndexOf(protoId); + SendPredictedMessage(new LabelableHolosignSignChosen(selected)); + } + + private void OpenDescriptionWindow(LabelableHolosignUIKey key) + { + SendPredictedMessage(new LabelableHolosignOpenOtherUI()); + } +} diff --git a/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml new file mode 100644 index 00000000000..cd6be988484 --- /dev/null +++ b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml @@ -0,0 +1,23 @@ + + + + diff --git a/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml.cs b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml.cs new file mode 100644 index 00000000000..30fb03f9276 --- /dev/null +++ b/Content.Client/_DEN/Holosign/UI/LabelableHolosignProjectorWindow.xaml.cs @@ -0,0 +1,51 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Utility; + +namespace Content.Client._DEN.Holosign.UI; + +[GenerateTypedNameReferences] +public sealed partial class LabelableHolosignProjectorWindow : DefaultWindow +{ + public event Action? OnDescriptionChanged; + + private string _description = string.Empty; + + public LabelableHolosignProjectorWindow() + { + RobustXamlLoader.Load(this); + + DescriptionTextEdit.OnTextChanged += e => + { + var text = Rope.Collapse(e.TextRope).Trim(); + _description = text; + OnDescriptionChanged?.Invoke(_description, IsNSFW.Pressed); + }; + + IsNSFW.OnPressed += e => + { + var text = Rope.Collapse(DescriptionTextEdit.TextRope).Trim(); + _description = text; + OnDescriptionChanged?.Invoke(_description, IsNSFW.Pressed); + }; + } + + public void SetCurrentDescription(string description) + { + if (description == _description) + return; + + _description = description; + if (!DescriptionTextEdit.HasKeyboardFocus()) + { + var rope = new Rope.Leaf(description); + DescriptionTextEdit.TextRope = rope; + } + } + + public void SetCurrentNsfw(bool isNsfw) + { + IsNSFW.Pressed = isNsfw; + } +} diff --git a/Content.Server/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs b/Content.Server/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs new file mode 100644 index 00000000000..2b40bde7400 --- /dev/null +++ b/Content.Server/_DEN/Holosign/Systems/LabelableHolosignProjectorSystem.cs @@ -0,0 +1,9 @@ +using Content.Shared._DEN.Holosign.Systems; + + +namespace Content.Server._DEN.Holosign.Systems; + + +public sealed class LabelableHolosignProjectorSystem : SharedLabelableHolosignProjectorSystem +{ +} diff --git a/Content.Shared/Climbing/Components/ClimbableComponent.cs b/Content.Shared/Climbing/Components/ClimbableComponent.cs index 22a42dea780..0032e6afe55 100644 --- a/Content.Shared/Climbing/Components/ClimbableComponent.cs +++ b/Content.Shared/Climbing/Components/ClimbableComponent.cs @@ -38,5 +38,11 @@ public sealed partial class ClimbableComponent : Component /// [DataField("finishClimbSound")] public SoundSpecifier? FinishClimbSound = null; + + // DEN: Allow climbing while clumsy + /// + /// Should this be climbable even while clumsy? + /// + [DataField] public bool BypassClumsy = false; } } diff --git a/Content.Shared/Clumsy/ClumsySystem.cs b/Content.Shared/Clumsy/ClumsySystem.cs index e4a23914620..48e9df59435 100644 --- a/Content.Shared/Clumsy/ClumsySystem.cs +++ b/Content.Shared/Clumsy/ClumsySystem.cs @@ -133,6 +133,10 @@ private void OnBeforeClimbEvent(Entity ent, ref SelfBeforeClimb if (!ent.Comp.ClumsyVaulting) return; + // DEN: Allow climbables to bypass clumsy. + if (args.BeingClimbedOn.Comp.BypassClumsy) + return; + if (!_cfg.GetCVar(CCVars.GameTableBonk) && !SharedRandomExtensions.PredictedProb(_timing, ent.Comp.ClumsyDefaultCheck, GetNetEntity(ent))) return; diff --git a/Content.Shared/_DEN/Holosign/Components/LabelableHolosignProjectorComponent.cs b/Content.Shared/_DEN/Holosign/Components/LabelableHolosignProjectorComponent.cs new file mode 100644 index 00000000000..b125152ef80 --- /dev/null +++ b/Content.Shared/_DEN/Holosign/Components/LabelableHolosignProjectorComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared._DEN.Holosign.Systems; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DEN.Holosign.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedLabelableHolosignProjectorSystem))] +public sealed partial class LabelableHolosignProjectorComponent : Component +{ + /// + /// The entity to spawn with this projector. + /// + [DataField(required: true), Access(Other = AccessPermissions.ReadWriteExecute)] + public List SignProtos; + + [DataField, AutoNetworkedField] public EntProtoId? SelectedSignProto; + + [DataField] + public EntityWhitelist SignWhitelist; + + [DataField] + public bool UsesCharges = false; + + [ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)] + [DataField, AutoNetworkedField] + public string BarrierDescription = string.Empty; + + /// + /// The maximum length of a description that can be attached to a barrier. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] + public int MaxDescriptionChars = 512; + + [DataField, AutoNetworkedField] + public bool IsNsfw; +} \ No newline at end of file diff --git a/Content.Shared/_DEN/Holosign/Components/LabeledHolosignComponent.cs b/Content.Shared/_DEN/Holosign/Components/LabeledHolosignComponent.cs new file mode 100644 index 00000000000..ef18c55b385 --- /dev/null +++ b/Content.Shared/_DEN/Holosign/Components/LabeledHolosignComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared._DEN.Holosign.Systems; +using Robust.Shared.GameStates; + + +namespace Content.Shared._DEN.Holosign.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedLabelableHolosignProjectorSystem))] +public sealed partial class LabeledHolosignComponent : Component +{ + [DataField, AutoNetworkedField] + public string Description; + + [DataField, AutoNetworkedField] + public bool IsNSFW; +} diff --git a/Content.Shared/_DEN/Holosign/Events/LabelableHolosignEvents.cs b/Content.Shared/_DEN/Holosign/Events/LabelableHolosignEvents.cs new file mode 100644 index 00000000000..8610f045985 --- /dev/null +++ b/Content.Shared/_DEN/Holosign/Events/LabelableHolosignEvents.cs @@ -0,0 +1,26 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._DEN.Holosign.Events; + +[Serializable, NetSerializable] +public enum LabelableHolosignUIKey +{ + Signs, + Description, +} + +[Serializable, NetSerializable] +public sealed class LabelableHolosignDescriptionMessage(string description, bool isNsfw) : BoundUserInterfaceMessage +{ + public string Description { get; } = description; + public bool IsNsfw { get; } = isNsfw; +} + +[Serializable, NetSerializable] +public sealed class LabelableHolosignSignChosen(int selection) : BoundUserInterfaceMessage +{ + public int Selection { get; } = selection; +} + +[Serializable, NetSerializable] +public sealed class LabelableHolosignOpenOtherUI : BoundUserInterfaceMessage; \ No newline at end of file diff --git a/Content.Shared/_DEN/Holosign/Systems/SharedLabelableHolosignProjectorSystem.cs b/Content.Shared/_DEN/Holosign/Systems/SharedLabelableHolosignProjectorSystem.cs new file mode 100644 index 00000000000..d93a801f94a --- /dev/null +++ b/Content.Shared/_DEN/Holosign/Systems/SharedLabelableHolosignProjectorSystem.cs @@ -0,0 +1,203 @@ +using System.Linq; +using Content.Shared._DEN.Consent.EntitySystems; +using Content.Shared._DEN.Consent.Prototypes; +using Content.Shared._DEN.Holosign.Components; +using Content.Shared._DEN.Holosign.Events; +using Content.Shared.Administration.Logs; +using Content.Shared.Charges.Components; +using Content.Shared.Charges.Systems; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.Database; +using Content.Shared.Examine; +using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Storage; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + + +namespace Content.Shared._DEN.Holosign.Systems; + +public abstract partial class SharedLabelableHolosignProjectorSystem : EntitySystem +{ + [Dependency] protected SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] private ISharedAdminLogManager _adminLogger = default!; + [Dependency] private SharedTransformSystem _transform = default!; + [Dependency] private SharedChargesSystem _charges = default!; + [Dependency] private EntityLookupSystem _lookup = default!; + [Dependency] private SharedPopupSystem _popup = default!; + [Dependency] private EntityWhitelistSystem _whitelist = default!; + [Dependency] private SharedConsentSystem _consent = default!; + [Dependency] private IPrototypeManager _prototypeManager = default!; + + private readonly ProtoId _nsfwDescriptionsConsent = "NSFWDescriptions"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHandleState); + SubscribeLocalEvent(OnBeforeInteract); + SubscribeLocalEvent(OnHolosignDescriptionChanged); + SubscribeLocalEvent(OnSignChosen); + SubscribeLocalEvent(OnOpenOtherUI); + SubscribeLocalEvent(OnProjectorExamined); + + SubscribeLocalEvent(OnSignExamine); + } + + private void OnSignExamine(EntityUid uid, LabeledHolosignComponent component, ExaminedEvent args) + { + if (component.IsNSFW) + { + if(_consent.HasConsent(args.Examiner, _nsfwDescriptionsConsent)) + args.PushMarkup(component.Description); + else + { + args.PushMarkup(Loc.GetString("labelable-holoprojector-consent-not-available")); + } + } + else + { + args.PushMarkup(component.Description); + } + } + + private void OnProjectorExamined(Entity entity, ref ExaminedEvent evt) + { + if (entity.Comp.SelectedSignProto is { } signProtoId && _prototypeManager.TryIndex(signProtoId, out var signProto)) + { + evt.PushMarkup(Loc.GetString("labelable-holoprojector-selected-sign", ("sign", signProto.Name))); + } + } + + private void OnBeforeInteract(Entity ent, ref BeforeRangedInteractEvent args) + { + if (args.Handled || !args.CanReach || + HasComp(args.Target) || + HasComp(args.Target)) + return; + + var coords = args.ClickLocation.SnapToGrid(EntityManager); + var mapCoords = _transform.ToMapCoordinates(coords); + + var matches = _lookup.GetEntitiesInRange(mapCoords, 0.25f); + matches.RemoveWhere(match => _whitelist.IsWhitelistFail(ent.Comp.SignWhitelist, match)); + + args.Handled = matches.Count == 0 ? TryPlaceSign(ent, args, args.User) : TryRemoveSign(ent, matches.First(), args.User); + } + + private bool TryPlaceSign(Entity ent, BeforeRangedInteractEvent args, EntityUid user) + { + if (ent.Comp.SelectedSignProto == null) + { + if (!_uiSystem.HasUi(ent, LabelableHolosignUIKey.Signs)) + return false; + _uiSystem.OpenUi(ent.Owner, LabelableHolosignUIKey.Signs, user); + UpdateUI(ent); + return true; + } + + if (ent.Comp.BarrierDescription.Length == 0) + { + if (!_uiSystem.HasUi(ent, LabelableHolosignUIKey.Description)) + return false; + _uiSystem.OpenUi(ent.Owner, LabelableHolosignUIKey.Description, user); + UpdateUI(ent); + return true; + } + + if (ent.Comp.UsesCharges) + { + if (!TryComp(ent, out var charges) || !_charges.TryUseCharge((ent, charges))) + { + _popup.PopupClient(Loc.GetString("labelable-holoprojector-no-charges", ("item", ent)), ent, args.User); + return false; + } + } + + var holoUid = PredictedSpawnAtPosition( + ent.Comp.SelectedSignProto, + args.ClickLocation); + + var labelComp = EnsureComp(holoUid); + labelComp.Description = ent.Comp.BarrierDescription; + labelComp.IsNSFW = ent.Comp.IsNsfw; + Dirty(holoUid, labelComp); + + var xform = Transform(holoUid); + xform.LocalRotation = Angle.Zero; + if (!xform.Anchored) + _transform.AnchorEntity(holoUid, xform); + + var nsfwStr = labelComp.IsNSFW ? "nsfw" : ""; + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):user} placed a {ToPrettyString(holoUid):holosign} with {nsfwStr} description {labelComp.Description}"); + + return true; + } + + private void OnHandleState(Entity ent, ref AfterAutoHandleStateEvent evt) + { + UpdateUI(ent); + } + + private bool TryRemoveSign(Entity ent, EntityUid sign, EntityUid user) + { + if (ent.Comp.UsesCharges && TryComp(ent, out var charges)) + _charges.AddCharges((ent, charges), 1); + + var userIdentity = Identity.Name(user, EntityManager); + _popup.PopupPredicted( + Loc.GetString("labelable-holoprojector-reclaim", ("sign", sign)), + Loc.GetString("labelable-holoprojector-reclaim-others", ("sign", sign), ("user", userIdentity)), + ent, + user); + + PredictedDel(sign); + + return true; + } + + protected virtual void UpdateUI(Entity entity) + { + } + + private void OnHolosignDescriptionChanged( + EntityUid uid, + LabelableHolosignProjectorComponent component, + LabelableHolosignDescriptionMessage args + ) + { + var description = args.Description.Trim(); + component.BarrierDescription = description[..Math.Min(component.MaxDescriptionChars, description.Length)]; + component.IsNsfw = args.IsNsfw; + UpdateUI((uid, component)); + Dirty(uid, component); + } + + private void OnSignChosen(Entity entity, ref LabelableHolosignSignChosen args) + { + // Use an index because trusting the client to send an arbitrary ProtoId seems bad. + if (entity.Comp.SignProtos.TryGetValue(args.Selection, out var signProtoId) && + _prototypeManager.TryIndex(signProtoId, out var proto)) + { + entity.Comp.SelectedSignProto = proto; + UpdateUI(entity); + Dirty(entity); + } + } + + private void OnOpenOtherUI(Entity entity, + ref LabelableHolosignOpenOtherUI args) + { + // Have to send this over here to open the BUI since I can't seem to do it from inside the UI. + var user = args.Actor; + if (!_uiSystem.HasUi(entity, LabelableHolosignUIKey.Description)) + return; + _uiSystem.OpenUi(entity.Owner, LabelableHolosignUIKey.Description, user); + UpdateUI(entity); + } +} diff --git a/Content.Shared/_DEN/Whitelist/HolobarrierComponent.cs b/Content.Shared/_DEN/Whitelist/HolobarrierComponent.cs new file mode 100644 index 00000000000..97e660a989c --- /dev/null +++ b/Content.Shared/_DEN/Whitelist/HolobarrierComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._DEN.Whitelist; + +/// +/// Marker component for holobarriers, used for reclaiming charges of the projector. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class HolobarrierComponent : Component; diff --git a/Resources/Locale/en-US/_DEN/holoprojector/labelable_holosign.ftl b/Resources/Locale/en-US/_DEN/holoprojector/labelable_holosign.ftl new file mode 100644 index 00000000000..6c021b26b6b --- /dev/null +++ b/Resources/Locale/en-US/_DEN/holoprojector/labelable_holosign.ftl @@ -0,0 +1,9 @@ +labelable-holoprojector-reclaim = You stop projecting {THE($sign)}. +labelable-holoprojector-reclaim-others = {CAPITALIZE($user)} stops projecting {THE($sign)}. +labelable-holoprojector-no-charges = {CAPITALIZE(THE($item))} is empty, reclaim an old holo first! +labelable-holoprojector-consent-not-available = Additional information is hidden by your consent settings. +labelable-holoprojector-ui-header = Labelable Holoprojector +labelable-holoprojector-current-text-label = Description: +labelable-holoprojector-is-nsfw-label = Requires NSFW Consent? +labelable-holoprojector-ui-set-description = Set the description of your projected barrier. +labelable-holoprojector-selected-sign = Currently selected: {$sign} diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 7045fd3a24d..1bf97d05bed 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -142,6 +142,7 @@ - ServiceStatic - PowerCellsStatic - ElectronicsStatic + - DenSharedStatic # DEN Shared static recipes. - type: EmagLatheRecipes emagStaticPacks: - SecurityAmmoStatic diff --git a/Resources/Prototypes/_DEN/Consent/consent.yml b/Resources/Prototypes/_DEN/Consent/consent.yml index 956ac5831bc..66579b247ac 100644 --- a/Resources/Prototypes/_DEN/Consent/consent.yml +++ b/Resources/Prototypes/_DEN/Consent/consent.yml @@ -3,4 +3,7 @@ defaultValue: true - type: consent - id: Mew \ No newline at end of file + id: Mew + +- type: consent + id: NSFWDescriptions \ No newline at end of file diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml b/Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml new file mode 100644 index 00000000000..780185eb4dc --- /dev/null +++ b/Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml @@ -0,0 +1,50 @@ +- type: entity + abstract: true + parent: BaseItem + id: BaseIndicatorHoloprojector + categories: [ HideSpawnMenu ] + components: + - type: Item + storedRotation: -90 + - type: UseDelay + delay: 2.0 + - type: ActivatableUI + key: enum.LabelableHolosignUIKey.Signs + inHandsOnly: true + - type: UserInterface + interfaces: + enum.LabelableHolosignUIKey.Signs: + type: LabelableHolosignProjectorSignBUI + enum.LabelableHolosignUIKey.Description: + type: LabelableHolosignProjectorDescriptionBUI + - type: LimitedCharges + maxCharges: 8 + charges: 8 + - type: AutoRecharge + rechargeDuration: 60 + - type: Tag + tags: + - HolofanProjector + - CivilianHoloprojector + - type: StaticPrice + price: 80 + +- type: entity + parent: BaseIndicatorHoloprojector + id: HoloprojectorPrivate + name: privacy holobarrier projector + description: Creates a holographic barrier indicating a private area. + components: + - type: Sprite + sprite: _DEN/Objects/Devices/Holoprojectors/private.rsi + state: icon + - type: LabelableHolosignProjector + signProtos: + - HolosignNSFW + - HolosignPrivate + isNsfw: false + usesCharges: true + signWhitelist: + components: + - Holobarrier + - LabeledHolosign diff --git a/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml b/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml new file mode 100644 index 00000000000..58dd6284f84 --- /dev/null +++ b/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml @@ -0,0 +1,86 @@ +- type: entity + abstract: true + id: BaseHolosign + placement: + mode: SnapgridCenter + components: + - type: Transform + anchored: true + - type: Physics + bodyType: Static + canCollide: false + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 30 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + +- type: entity + abstract: true + id: BaseIndicatorHolosign + placement: + mode: SnapgridCenter + components: + - type: Physics + bodyType: Static + canCollide: true + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.49,-0.49,0.49,0.49" + mask: + - TableMask + layer: + - TableLayer + - LowImpassable + - type: Holobarrier + - type: PointLight + enabled: true + radius: 3 + color: pink + - type: Climbable + bypassClumsy: true + - type: Clickable + - type: Occluder + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 500 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + +- type: entity + id: HolosignNSFW + parent: [ BaseIndicatorHolosign, BaseHolosign ] + name: nsfw holographic barrier + description: A barrier of hard light that indicates an area where adult activities are occurring. + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _DEN/Structures/Holo/adult.rsi + state: icon + drawdepth: Overdoors + +- type: entity + id: HolosignPrivate + parent: [ BaseIndicatorHolosign, BaseHolosign ] + name: private holographic barrier + description: A barrier of hard light that indicates an area where private activities are occurring. + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _DEN/Structures/Holo/private.rsi + state: icon + drawdepth: Overdoors + diff --git a/Resources/Prototypes/_DEN/Recipes/Lathes/Packs/shared.yml b/Resources/Prototypes/_DEN/Recipes/Lathes/Packs/shared.yml new file mode 100644 index 00000000000..dd84f63413d --- /dev/null +++ b/Resources/Prototypes/_DEN/Recipes/Lathes/Packs/shared.yml @@ -0,0 +1,4 @@ +- type: latheRecipePack + id: DenSharedStatic + recipes: + - HoloprojectorPrivate \ No newline at end of file diff --git a/Resources/Prototypes/_DEN/Recipes/Lathes/devices.yml b/Resources/Prototypes/_DEN/Recipes/Lathes/devices.yml new file mode 100644 index 00000000000..ab15927881b --- /dev/null +++ b/Resources/Prototypes/_DEN/Recipes/Lathes/devices.yml @@ -0,0 +1,7 @@ +- type: latheRecipe + parent: BaseToolRecipe + id: HoloprojectorPrivate + result: HoloprojectorPrivate + materials: + Steel: 100 + Plastic: 100 \ No newline at end of file diff --git a/Resources/Prototypes/_DEN/tags.yml b/Resources/Prototypes/_DEN/tags.yml index 21c486c91c5..17dbcfcd575 100644 --- a/Resources/Prototypes/_DEN/tags.yml +++ b/Resources/Prototypes/_DEN/tags.yml @@ -3,3 +3,6 @@ - type: Tag id: TeddyRibbon # Ribbon slot for teddy bears + +- type: Tag + id: CivilianHoloprojector \ No newline at end of file diff --git a/Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/icon-inhand-left.png b/Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/icon-inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..26791827d93a31dc9eb26b240a6d5b5236f4e49f GIT binary patch literal 1967 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 Bhu{DJ literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/icon.png b/Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb094888d18f8345d1ed10c0a17a21fbef546977 GIT binary patch literal 1169 zcmV;C1aA9@P)Fl^oSE zn&WVgMH&)BD5zo+I$R{Fnv_|0U#jU_Muj|#Rtq)QZ(%JNLT68QRBJ) z&(qOnrv2^_2?)J<-sc1m-Uk|W&-*>~yv7+2d;zY_w!cC-&3%(zZ)@>mAi4uw-n2F4 z0Jz!(MxQO&mYY(Lrl4uS#|1D{0EX{@fptHt)_VyRn`GzRvXd<0H*oCDpPg^M-<#con@5BK0w4eaAOHd&00JNY0{y-1A>QL&I#4+Xttw(9qQzT~lqodO@DI z=$D96L5}KQe%Vj8>+Mj-g}w-Y&9lKRZ;@6O9{9D%`^qpEHmJ=n->Gx~xbW<;sxNuu z>xJ$HTiPK}eHwe$nWC6jzqGay8-zaF4yaTrD%xRmw;rnDTxTHnjviB2UD)WO;wa5s zyGabh+!ecNafIVOJM>r2IoG!Wn4OvEUi08z09^9t&(f1`U)42Q>h*fA(P-GX_S|Rc zw?3{r*S7+oAmaQyt76uJ<%In~ogwg7v%P2MdZ3{)R)$Weh>fw5C8!X009sH0T2KI5C8!X z009sH0T2KI5CDOHiGcXGxMU{n`Mfm0)Sp&gJRaZc^Lfc&m;UL>?8p!3s_oX;aaK>Dq-^dmn2`2i`m4{ikk5C8!X j009sH0T2LzSOR|l5t_0N?P(fn00000NkvXXu0mjfnNA?l literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/meta.json b/Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/meta.json new file mode 100644 index 00000000000..13e2ac6cc8b --- /dev/null +++ b/Resources/Textures/_DEN/Objects/Devices/Holoprojectors/private.rsi/meta.json @@ -0,0 +1,82 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC0-1.0", + "copyright": "Edited from tgstation at https://github.com/tgstation/tgstation/commit/f4017da82ae4de7bfaf8ebdbba33d0de81c15587. Edited from TiniestShark's inhands. Editing by Leonardo DaBepis", + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "icon-inhand-left", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "icon-inhand-right", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + } + ] +} diff --git a/Resources/Textures/_DEN/Structures/Holo/adult.rsi/icon.png b/Resources/Textures/_DEN/Structures/Holo/adult.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8965c6b4504fc3a3cbd3feb1a6e3cb7be6da9556 GIT binary patch literal 1487 zcmYjP3pmqj82{F+DVJF{YwQfCIyy->3QLw-ky@!$7BZJEq9R47*#2$8?$(;Y{JkL4b^Ss~t{oe0;zW4pT?~k*GvfW@YelY;R zz}w4XFYM{C-O*hL@8eP7X|O}Y?A`8$=z3+vgn`a6*Iljv2)KG*gHbRx3-#K&3xEr2 z0Duw!P{Sce4!}7Z02FipNSOc_M`cy{I>Q%>qP%D^02tP54RLLqp$PyeM{f^T>iO!Cx?}qq9G?Nas(R$3pYROwwF6NGzNU0LS0gA3Qx!p%7ZFA8MRJ&CRD#+1U2> zlcXD`+Bum)lv^&iq$0^%07tNF%eIP)jQpM@e~CUWqdM*H$x@4zVzeR>lOQ$lEtK^I zrci_eZ533c)xzlwHh51@2g@ug|GAq2kd*{X#*g(E%>176=8i=2Gw-p2Kr zF}Z9o?1c3svNqEWBRQ+cj&pwF4O?2cHC)#CfH1QF=W#3jnRMd4HA2?pVCya>V(FfI zM1|~x@y6Z*J*2thBC_4`#7}jY{md{6-Gi5jcQw2Sqf$Mx{hVKDTEKFTFERIXyX-p* zcaRTM`q-a;WBKl6QXV&$ckG$C{?gHqfe9kd4J=6JE(uYb)%=+bMoc4SGpCK^}N8@IjZ&z;vb8l!nTaYyvYABE zLSQ!$jhbd>D?_1wU%{=QRECS^^LaJL#a+S^ke!OLHBO3glnMIhD|6>oHSBixG5$H8 zTw5+Wt$|EX38{&qM|m}7cQ3P=Mf-llSuulZh~+6o>m=7ig0UBYTpc_A87^K;ZR_Ni z+qUU0Y|vKE2xvts*TX{(SFrmrpc!`qPdHvK>~e_vI1l6RYZNe{&2xUManYYIIt9{W z7%pthuY#rwetgxgdltLBa*8($W;agdk6E&c7O!|jHfzY>S7)slPfaTHiBqJ8p;+2> zxGIpzsv*ov!pQa|Fgv$){dA=50`z;vgYfy{(smuAG9;za%fGpu-A&QU5VEBswvYzPv!4tzX`(T!?y!2 z>B1)c=hMjZlZfw>a+g$*Ygcdkig;L!C{jNRjhfNc-eFG;IwE5Dr=~8yPSQYQQ%^po zcAs5tJv7`|GJLT!v>MaV)fbmwWTaw18w`x#FOq$Y8fVA#oJW6B+3HV*-4^ZNbq(i- zhsFt-tUjnO5Sb*$jLLjH*Y%B`O~a!kC;v{kJ?S zBfE59=il{@f4Nh*phBhyKJ=yS1PxWpiH)dYl&_4Z%oP|*FqX}t$V#f670 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DEN/Structures/Holo/adult.rsi/meta.json b/Resources/Textures/_DEN/Structures/Holo/adult.rsi/meta.json new file mode 100644 index 00000000000..5d0fd3d24c7 --- /dev/null +++ b/Resources/Textures/_DEN/Structures/Holo/adult.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-4.0", + "copyright": "Made by Dirius77", + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} diff --git a/Resources/Textures/_DEN/Structures/Holo/private.rsi/icon.png b/Resources/Textures/_DEN/Structures/Holo/private.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5a54cd34031383887770b8c8522ff9063d8b1f12 GIT binary patch literal 2954 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_DEN/Structures/Holo/private.rsi/meta.json b/Resources/Textures/_DEN/Structures/Holo/private.rsi/meta.json new file mode 100644 index 00000000000..ba1fc40b5c6 --- /dev/null +++ b/Resources/Textures/_DEN/Structures/Holo/private.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC0-1.0", + "copyright": "Made by Leonardo DaBepis", + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} From 9a0d7e94078d82ad4908ee4925afd21f502d521f Mon Sep 17 00:00:00 2001 From: Dirius Date: Wed, 3 Jun 2026 18:21:14 -0400 Subject: [PATCH 2/3] Missed some field changes --- .../Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml | 1 - .../_DEN/Entities/Structures/Holographic/projections.yml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml b/Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml index 780185eb4dc..d61d2ea1f78 100644 --- a/Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml +++ b/Resources/Prototypes/_DEN/Entities/Objects/Devices/holoprojectors.yml @@ -19,7 +19,6 @@ type: LabelableHolosignProjectorDescriptionBUI - type: LimitedCharges maxCharges: 8 - charges: 8 - type: AutoRecharge rechargeDuration: 60 - type: Tag diff --git a/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml b/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml index 58dd6284f84..43754f2b6bd 100644 --- a/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml +++ b/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml @@ -10,6 +10,7 @@ bodyType: Static canCollide: false - type: Damageable + - type: Injurable damageContainer: Inorganic - type: Destructible thresholds: From 6e9fbd14986980f42862cefe8c60b37e00232f95 Mon Sep 17 00:00:00 2001 From: Dirius Date: Wed, 3 Jun 2026 18:34:21 -0400 Subject: [PATCH 3/3] Allow putting projectors into PDAs --- Content.Shared/PDA/PdaComponent.cs | 5 ++++ Content.Shared/PDA/SharedPdaSystem.cs | 2 ++ .../Entities/Objects/Devices/pda.yml | 5 ++++ .../Structures/Holographic/projections.yml | 30 ++++--------------- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/Content.Shared/PDA/PdaComponent.cs b/Content.Shared/PDA/PdaComponent.cs index cdfeffa2c1c..1745646c32a 100644 --- a/Content.Shared/PDA/PdaComponent.cs +++ b/Content.Shared/PDA/PdaComponent.cs @@ -12,6 +12,7 @@ public sealed partial class PdaComponent : Component public const string PdaIdSlotId = "PDA-id"; public const string PdaPenSlotId = "PDA-pen"; public const string PdaPaiSlotId = "PDA-pai"; + public const string ProjectorSlotId = "PDA-projector"; // DEN: Projector slot in PDA. [DataField("idSlot")] public ItemSlot IdSlot = new(); @@ -20,6 +21,10 @@ public sealed partial class PdaComponent : Component public ItemSlot PenSlot = new(); [DataField("paiSlot")] public ItemSlot PaiSlot = new(); + + // DEN: Allow slotting civilian projectors into the PDA. + [DataField("projectorSlot")] + public ItemSlot ProjectorSlot = new(); // Really this should just be using ItemSlot.StartingItem. However, seeing as we have so many different starting // PDA's and no nice way to inherit the other fields from the ItemSlot data definition, this makes the yaml much diff --git a/Content.Shared/PDA/SharedPdaSystem.cs b/Content.Shared/PDA/SharedPdaSystem.cs index 6eb665747d3..fb656ec3303 100644 --- a/Content.Shared/PDA/SharedPdaSystem.cs +++ b/Content.Shared/PDA/SharedPdaSystem.cs @@ -31,6 +31,7 @@ protected virtual void OnComponentInit(EntityUid uid, PdaComponent pda, Componen ItemSlotsSystem.AddItemSlot(uid, PdaComponent.PdaIdSlotId, pda.IdSlot); ItemSlotsSystem.AddItemSlot(uid, PdaComponent.PdaPenSlotId, pda.PenSlot); ItemSlotsSystem.AddItemSlot(uid, PdaComponent.PdaPaiSlotId, pda.PaiSlot); + ItemSlotsSystem.AddItemSlot(uid, PdaComponent.ProjectorSlotId, pda.ProjectorSlot); // DEN: Projector Slot UpdatePdaAppearance(uid, pda); } @@ -40,6 +41,7 @@ private void OnComponentRemove(EntityUid uid, PdaComponent pda, ComponentRemove ItemSlotsSystem.RemoveItemSlot(uid, pda.IdSlot); ItemSlotsSystem.RemoveItemSlot(uid, pda.PenSlot); ItemSlotsSystem.RemoveItemSlot(uid, pda.PaiSlot); + ItemSlotsSystem.RemoveItemSlot(uid, pda.ProjectorSlot); // DEN: Projector Slot } protected virtual void OnItemInserted(EntityUid uid, PdaComponent pda, EntInsertedIntoContainerMessage args) diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index ad3b59b0eaf..3055f1ac959 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -45,6 +45,11 @@ whitelist: components: - IdCard + projectorSlot: + priority: -2 + whitelist: + tags: + - CivilianHoloprojector - type: PdaVisuals - type: Item size: Small diff --git a/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml b/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml index 43754f2b6bd..50ddb1f437a 100644 --- a/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml +++ b/Resources/Prototypes/_DEN/Entities/Structures/Holographic/projections.yml @@ -1,26 +1,3 @@ -- type: entity - abstract: true - id: BaseHolosign - placement: - mode: SnapgridCenter - components: - - type: Transform - anchored: true - - type: Physics - bodyType: Static - canCollide: false - - type: Damageable - - type: Injurable - damageContainer: Inorganic - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 30 - behaviors: - - !type:DoActsBehavior - acts: ["Destruction"] - - type: entity abstract: true id: BaseIndicatorHolosign @@ -50,6 +27,9 @@ bypassClumsy: true - type: Clickable - type: Occluder + - type: Damageable + - type: Injurable + damageContainer: Inorganic - type: Destructible thresholds: - trigger: @@ -61,7 +41,7 @@ - type: entity id: HolosignNSFW - parent: [ BaseIndicatorHolosign, BaseHolosign ] + parent: [ BaseIndicatorHolosign ] name: nsfw holographic barrier description: A barrier of hard light that indicates an area where adult activities are occurring. placement: @@ -74,7 +54,7 @@ - type: entity id: HolosignPrivate - parent: [ BaseIndicatorHolosign, BaseHolosign ] + parent: [ BaseIndicatorHolosign ] name: private holographic barrier description: A barrier of hard light that indicates an area where private activities are occurring. placement: