This commit is contained in:
Coryn 2026-05-10 11:43:47 +00:00 committed by GitHub
commit 1801c3b9bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 381 additions and 1 deletions

View File

@ -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;
/// <summary>
/// Systems related to the Metal Detector and how it functions.
/// </summary>
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<MetalDetectorComponent, StepTriggeredOnEvent>(HandleStepOnTriggered);
SubscribeLocalEvent<MetalDetectorComponent, StepTriggeredOffEvent>(HandleStepOffTriggered);
SubscribeLocalEvent<MetalDetectorComponent, StepTriggerAttemptEvent>(HandleStepTriggerAttempt);
SubscribeLocalEvent<MetalDetectorComponent, GotEmaggedEvent>(OnEmagged);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
using var query = EntityQueryEnumerator<MetalDetectorComponent>();
while (query.MoveNext(out var ent, out var comp))
{
if (!comp.IsSirenRunning || !TryComp<ItemToggleComponent>(ent, out var toggle) || !toggle.Activated)
continue;
var powered = TryComp<ApcPowerReceiverComponent>(ent, out var receiver) && receiver.Powered;
TryComp<AppearanceComponent>(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<MetalDetectorComponent> ent, ref StepTriggeredOnEvent args)
{
if (ent.Comp.IsSirenRunning)
{
ent.Comp.EndOfSirenSound = _timing.CurTime + ent.Comp.SirenRunTime;
return;
}
var random = new Random();
if (TryComp<ItemToggleComponent>(ent, out var toggleComponent) && (CheckForContraband(args.Tripper)
|| random.NextFloat(0.0f, 100.0f) < ent.Comp.FalsePositiveChance
|| HasComp<EmaggedComponent>(ent)))
{
toggleComponent.Activated = true;
TryComp<AppearanceComponent>(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<MetalDetectorComponent> ent, ref StepTriggeredOffEvent args)
{
// Start timer to deactivate the siren
ent.Comp.IsSirenRunning = TryComp<ItemToggleComponent>(ent, out var toggleComponent) && toggleComponent.Activated;
}
private void HandleStepTriggerAttempt(Entity<MetalDetectorComponent> ent, ref StepTriggerAttemptEvent args)
{
args.Continue = TryComp<ApcPowerReceiverComponent>(ent, out var receiver) && receiver.Powered;
}
private void OnEmagged(Entity<MetalDetectorComponent> 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<ContrabandComponent>(characterUid))
return true;
if (_containerSystem.TryGetContainer(characterUid, ImplanterComponent.ImplantSlotId, out var implants))
{
var storageImplanter = implants.ContainedEntities.ToList().Find(HasComp<StorageComponent>);
if (TryComp<StorageComponent>(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<ContrabandComponent>(item, out var contrabandComp))
return false;
if (!_idSystem.TryFindIdCard(characterUid, out var idCard))
return true;
return !idCard.Comp.JobDepartments.Intersect(contrabandComp.AllowedDepartments).Any();
}
}

View File

@ -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;
/// <summary>
/// Responsible for holding some data of whom has passed by and which item is the most dangerous.
/// </summary>
[RegisterComponent]
public sealed partial class MetalDetectorComponent : Component
{
[DataField]
public TimeSpan SirenRunTime = TimeSpan.FromSeconds(10);
/// <summary>
/// Value to determine the chance that the Metal Detector simply fires and gives a false positive. from 0 to 100.
/// </summary>
[DataField]
public float FalsePositiveChance = 5.0f;
/// <summary>
/// Timespan which is set runtime to determine when the Siren should stop running
/// </summary>
[DataField]
public TimeSpan EndOfSirenSound = TimeSpan.FromSeconds(0);
public bool IsSirenRunning = false;
/// <summary>
/// Siren Sound which is played when the Metal Detector fires.
/// </summary>
[DataField]
public SoundSpecifier? SirenSound;
/// <summary>
/// The port that gets signaled when the the metal detector fires
/// </summary>
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<SourcePortPrototype>))]
public string TriggerPort = "Trigger";
}
[Serializable, NetSerializable]
public enum MetalDetectorVisuals : byte
{
MetalDetectorActivated,
}
[Serializable, NetSerializable]
public enum MetalDetectorVisualLayers : byte
{
MetalDetectorLayer,
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -64,6 +64,11 @@
recipes:
- Truncheon
- type: latheRecipePack
id: SecurityMachines
recipes:
- MetalDetectorMachineCircuitBoard
## Dynamic
- type: latheRecipePack

View File

@ -3,3 +3,9 @@
parent: [ BaseCircuitboardRecipe, BaseEngineeringMachineRecipeCategory ]
id: KitchenFreezerMachineCircuitBoard
result: KitchenFreezerMachineCircuitBoard
# Security
- type: latheRecipe
parent: [ BaseCircuitboardRecipe, BaseSecurityMachineRecipeCategory ]
id: MetalDetectorMachineCircuitBoard
result: MetalDetectorMachineCircuitBoard

View File

@ -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
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B