diff --git a/Content.Server/_DV/MetalDetector/MetalDetectorSystem.cs b/Content.Server/_DV/MetalDetector/MetalDetectorSystem.cs new file mode 100644 index 0000000000..fa57d51be7 --- /dev/null +++ b/Content.Server/_DV/MetalDetector/MetalDetectorSystem.cs @@ -0,0 +1,158 @@ +using System.Linq; +using Content.Server.DeviceLinking.Systems; +using Content.Server.Power.Components; +using Content.Shared._DV.MetalDetector; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.Contraband; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; +using Content.Shared.Implants.Components; +using Content.Shared.Inventory; +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.StepTrigger.Systems; +using Content.Shared.Storage; +using Content.Shared.Toggleable; +using Robust.Shared.Containers; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server._DV.MetalDetector; + +/// +/// Systems related to the Metal Detector and how it functions. +/// +public sealed class MetalDetectorSystem : EntitySystem +{ + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedIdCardSystem _idSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly EmagSystem _emag = default!; + [Dependency] private readonly DeviceLinkSystem _deviceLink = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandleStepOnTriggered); + SubscribeLocalEvent(HandleStepOffTriggered); + SubscribeLocalEvent(HandleStepTriggerAttempt); + SubscribeLocalEvent(OnEmagged); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + using var query = EntityQueryEnumerator(); + while (query.MoveNext(out var ent, out var comp)) + { + if (!comp.IsSirenRunning || !TryComp(ent, out var toggle) || !toggle.Activated) + continue; + + var powered = TryComp(ent, out var receiver) && receiver.Powered; + + TryComp(ent, out var appComp); + + if (comp.EndOfSirenSound <= _timing.CurTime || !powered) + { + comp.IsSirenRunning = false; + var toggledEvent = new ItemToggledEvent(false, false, ent); + toggle.Activated = false; + _appearanceSystem.SetData(ent, MetalDetectorVisuals.MetalDetectorActivated, false); + _appearanceSystem.SetData(ent, ToggleableVisuals.Enabled, false, appComp); + RaiseLocalEvent(ent, ref toggledEvent); + } + } + } + + private void HandleStepOnTriggered(Entity ent, ref StepTriggeredOnEvent args) + { + if (ent.Comp.IsSirenRunning) + { + ent.Comp.EndOfSirenSound = _timing.CurTime + ent.Comp.SirenRunTime; + return; + } + + var random = new Random(); + if (TryComp(ent, out var toggleComponent) && (CheckForContraband(args.Tripper) + || random.NextFloat(0.0f, 100.0f) < ent.Comp.FalsePositiveChance + || HasComp(ent))) + { + toggleComponent.Activated = true; + TryComp(ent, out var appComp); + _appearanceSystem.SetData(ent, MetalDetectorVisuals.MetalDetectorActivated, true); + _appearanceSystem.SetData(ent, ToggleableVisuals.Enabled, true, appComp); + var toggledEvent = new ItemToggledEvent(false, true, ent); + ent.Comp.EndOfSirenSound = _timing.CurTime + ent.Comp.SirenRunTime; + RaiseLocalEvent(ent, ref toggledEvent); + + _deviceLink.InvokePort(ent, ent.Comp.TriggerPort); + } + } + + private void HandleStepOffTriggered(Entity ent, ref StepTriggeredOffEvent args) + { + // Start timer to deactivate the siren + ent.Comp.IsSirenRunning = TryComp(ent, out var toggleComponent) && toggleComponent.Activated; + } + + private void HandleStepTriggerAttempt(Entity ent, ref StepTriggerAttemptEvent args) + { + args.Continue = TryComp(ent, out var receiver) && receiver.Powered; + } + + private void OnEmagged(Entity metalDetectorComponent, ref GotEmaggedEvent args) + { + if (!_emag.CompareFlag(args.Type, EmagType.Interaction)) + return; + + if (_emag.CheckFlag(metalDetectorComponent.Owner, EmagType.Interaction)) + return; + + args.Handled = true; + } + + private bool CheckForContraband(EntityUid characterUid) + { + // In it's just a contraband item + if (HasComp(characterUid)) + return true; + + if (_containerSystem.TryGetContainer(characterUid, ImplanterComponent.ImplantSlotId, out var implants)) + { + var storageImplanter = implants.ContainedEntities.ToList().Find(HasComp); + + if (TryComp(storageImplanter, out var storage)) + { + foreach (var stored in storage.Container.ContainedEntities) + { + if (IsEntityContraband(characterUid, stored)) + return true; + } + } + } + + foreach (var item in _inventorySystem.GetHandOrInventoryEntities(characterUid)) + { + if (IsEntityContraband(characterUid, item)) + return true; + } + + return false; + } + + private bool IsEntityContraband(EntityUid characterUid, EntityUid item) + { + if (!TryComp(item, out var contrabandComp)) + return false; + + if (!_idSystem.TryFindIdCard(characterUid, out var idCard)) + return true; + + return !idCard.Comp.JobDepartments.Intersect(contrabandComp.AllowedDepartments).Any(); + } + +} diff --git a/Content.Shared/_DV/MetalDetector/MetalDetectorComponent.cs b/Content.Shared/_DV/MetalDetector/MetalDetectorComponent.cs new file mode 100644 index 0000000000..ae4b1be89d --- /dev/null +++ b/Content.Shared/_DV/MetalDetector/MetalDetectorComponent.cs @@ -0,0 +1,54 @@ +using Content.Shared.DeviceLinking; +using Robust.Shared.Audio; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared._DV.MetalDetector; + +/// +/// Responsible for holding some data of whom has passed by and which item is the most dangerous. +/// +[RegisterComponent] +public sealed partial class MetalDetectorComponent : Component +{ + [DataField] + public TimeSpan SirenRunTime = TimeSpan.FromSeconds(10); + + /// + /// Value to determine the chance that the Metal Detector simply fires and gives a false positive. from 0 to 100. + /// + [DataField] + public float FalsePositiveChance = 5.0f; + + /// + /// Timespan which is set runtime to determine when the Siren should stop running + /// + [DataField] + public TimeSpan EndOfSirenSound = TimeSpan.FromSeconds(0); + + public bool IsSirenRunning = false; + + /// + /// Siren Sound which is played when the Metal Detector fires. + /// + [DataField] + public SoundSpecifier? SirenSound; + + /// + /// The port that gets signaled when the the metal detector fires + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string TriggerPort = "Trigger"; +} + +[Serializable, NetSerializable] +public enum MetalDetectorVisuals : byte +{ + MetalDetectorActivated, +} + +[Serializable, NetSerializable] +public enum MetalDetectorVisualLayers : byte +{ + MetalDetectorLayer, +} diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 11731005db..d3f9983e6a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -450,6 +450,7 @@ - SecurityAmmoStatic - SecurityWeaponsStatic # Being DeltaV Additions + - SecurityMachines - ReportingStatic - SecurityRubberAmmoStatic - SecurityShotgunDrumsStatic @@ -519,7 +520,7 @@ unlitRunningState: unlit-building # DeltaV - added animation sprite layers staticPacks: - SecurityAmmoStatic - # Start DeltaV Additions + # Start DeltaV Additions - SecurityShotgunDrumsStatic - AmmoFabPracticeStatic # Removes practice guns - SecurityRubberAmmoStatic diff --git a/Resources/Prototypes/_DV/Entities/Objects/Devices/CircuitBoards/Machine/production.yml b/Resources/Prototypes/_DV/Entities/Objects/Devices/CircuitBoards/Machine/production.yml index d9cdbe928d..4ab3422a94 100644 --- a/Resources/Prototypes/_DV/Entities/Objects/Devices/CircuitBoards/Machine/production.yml +++ b/Resources/Prototypes/_DV/Entities/Objects/Devices/CircuitBoards/Machine/production.yml @@ -101,3 +101,19 @@ stackRequirements: Manipulator: 4 Cable: 5 + +# Metal Detector +- type: entity + id: MetalDetectorMachineCircuitBoard + parent: BaseMachineCircuitboard + name: metal detector machine board + description: Makes beeps and boops when evil passes by + components: + - type: Sprite + state: security + - type: MachineBoard + prototype: MetalDetector + stackRequirements: + Manipulator: 4 + Glass: 5 + Cable: 5 diff --git a/Resources/Prototypes/_DV/Entities/Structures/Machines/metal_detector.yml b/Resources/Prototypes/_DV/Entities/Structures/Machines/metal_detector.yml new file mode 100644 index 0000000000..307f22ad6b --- /dev/null +++ b/Resources/Prototypes/_DV/Entities/Structures/Machines/metal_detector.yml @@ -0,0 +1,112 @@ +- type: entity + parent: ConstructibleMachine + id: MetalDetector + name: Metal Detector + description: Beeps when metal and contraband is detected by a passing person. + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _DV/Structures/Machines/metal_detector.rsi + layers: + - state: metal_detector_off + map: [ "enum.MetalDetectorVisualLayers.MetalDetectorLayer" ] + - type: GenericVisualizer + visuals: + enum.MetalDetectorVisuals.MetalDetectorActivated: + enum.MetalDetectorVisualLayers.MetalDetectorLayer: + True: { state: metal_detector_on } + False: { state: metal_detector_off } + - type: ApcPowerReceiver + powerLoad: 1000 + - type: Clickable + - type: InteractionOutline + - type: Pullable + - type: Machine + board: MetalDetectorMachineCircuitBoard + - type: Transform + anchored: true + noRot: true + - type: ExtensionCableReceiver + - type: LightningTarget + priority: 1 + - type: DeviceNetwork + deviceNetId: Wireless + - type: WirelessNetworkConnection + range: 200 + - type: DeviceLinkSource + ports: + - Trigger + - type: WiresPanel + - type: Wires + alwaysRandomize: true + boardName: wires-board-name-apc + layoutId: APC + - type: WiresVisuals + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 200 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + - !type:PlaySoundBehavior + sound: + collection: MetalBreak + - type: LockedWiresPanel + - type: LockedAnchorable + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Anchorable + - type: Physics + bodyType: Static + - type: ToggleableVisuals + spriteLayer: null + - type: ItemToggle + predictable: false # issues between ToggleCellDraw and ItemToggleActiveSound + onActivate: false + - type: ItemTogglePointLight + - type: PointLight + enabled: false + radius: 2.5 + energy: 3 + color: red + netsync: false + mask: /Textures/Effects/LightMasks/double_cone.png + - type: RotatingLight + speed: 360 + - type: ItemToggleActiveSound + activeSound: + path: /Audio/Effects/Vehicle/policesiren.ogg + params: + volume: -3 + - type: Appearance + - type: Fixtures + fixtures: + shape: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + mask: + - TabletopMachineMask + layer: + - TabletopMachineLayer + overlap: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + hard: false + layer: + - Impassable + - type: MetalDetector + - type: StepTrigger + requiredTriggeredSpeed: 0 + stepOn: true diff --git a/Resources/Prototypes/_DV/Recipes/Lathes/Packs/security.yml b/Resources/Prototypes/_DV/Recipes/Lathes/Packs/security.yml index 3c1fe93ac8..efcdb750fe 100644 --- a/Resources/Prototypes/_DV/Recipes/Lathes/Packs/security.yml +++ b/Resources/Prototypes/_DV/Recipes/Lathes/Packs/security.yml @@ -64,6 +64,11 @@ recipes: - Truncheon +- type: latheRecipePack + id: SecurityMachines + recipes: + - MetalDetectorMachineCircuitBoard + ## Dynamic - type: latheRecipePack diff --git a/Resources/Prototypes/_DV/Recipes/Lathes/machine_boards.yml b/Resources/Prototypes/_DV/Recipes/Lathes/machine_boards.yml index 343a21e83a..fcffa83fc1 100644 --- a/Resources/Prototypes/_DV/Recipes/Lathes/machine_boards.yml +++ b/Resources/Prototypes/_DV/Recipes/Lathes/machine_boards.yml @@ -3,3 +3,9 @@ parent: [ BaseCircuitboardRecipe, BaseEngineeringMachineRecipeCategory ] id: KitchenFreezerMachineCircuitBoard result: KitchenFreezerMachineCircuitBoard + +# Security +- type: latheRecipe + parent: [ BaseCircuitboardRecipe, BaseSecurityMachineRecipeCategory ] + id: MetalDetectorMachineCircuitBoard + result: MetalDetectorMachineCircuitBoard diff --git a/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/meta.json b/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/meta.json new file mode 100644 index 0000000000..b8d1637ac3 --- /dev/null +++ b/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/meta.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "From coryduck on the Delta-V Server Discord", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "metal_detector_off" + }, + { + "name": "metal_detector_on", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/metal_detector_off.png b/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/metal_detector_off.png new file mode 100644 index 0000000000..19b0a70c29 Binary files /dev/null and b/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/metal_detector_off.png differ diff --git a/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/metal_detector_on.png b/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/metal_detector_on.png new file mode 100644 index 0000000000..bf29c3ec69 Binary files /dev/null and b/Resources/Textures/_DV/Structures/Machines/metal_detector.rsi/metal_detector_on.png differ