From 79d34a02bb04e48847461acc108e5d1ba2f69722 Mon Sep 17 00:00:00 2001
From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Thu, 13 Mar 2025 08:39:19 +0000
Subject: [PATCH] add ai detector from ss13 (#3192)
* add CollidingVisuals system
* add AiDetectable to ai eye
* add AI detector item
* add AI detector to uplink, clean up observation kit too
* switch to component...
---------
Co-authored-by: deltanedas <@deltanedas:kde.org>
---
.../_DV/Physics/CollidingVisualsComponent.cs | 42 ++++++++++++
.../_DV/Physics/CollidingVisualsSystem.cs | 64 +++++++++++++++++++
.../_DV/Whitelist/AiDetectableComponent.cs | 9 +++
.../en-US/_DV/store/uplink/deception.ftl | 4 ++
.../Locale/en-US/store/uplink-catalog.ftl | 2 +-
.../Entities/Mobs/Player/silicon.yml | 1 +
.../_DV/Catalog/Uplink/deception.yml | 29 +++++++++
.../Prototypes/_DV/Catalog/uplink_catalog.yml | 14 ----
.../Entities/Objects/Tools/ai_detector.yml | 57 +++++++++++++++++
9 files changed, 207 insertions(+), 15 deletions(-)
create mode 100644 Content.Shared/_DV/Physics/CollidingVisualsComponent.cs
create mode 100644 Content.Shared/_DV/Physics/CollidingVisualsSystem.cs
create mode 100644 Content.Shared/_DV/Whitelist/AiDetectableComponent.cs
create mode 100644 Resources/Locale/en-US/_DV/store/uplink/deception.ftl
create mode 100644 Resources/Prototypes/_DV/Catalog/Uplink/deception.yml
create mode 100644 Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml
diff --git a/Content.Shared/_DV/Physics/CollidingVisualsComponent.cs b/Content.Shared/_DV/Physics/CollidingVisualsComponent.cs
new file mode 100644
index 0000000000..dfa1800652
--- /dev/null
+++ b/Content.Shared/_DV/Physics/CollidingVisualsComponent.cs
@@ -0,0 +1,42 @@
+using Content.Shared.Whitelist;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._DV.Physics;
+
+///
+/// Changes an appearance data string depending on active collisions with fixtures.
+///
+[RegisterComponent, Access(typeof(CollidingVisualsSystem))]
+public sealed partial class CollidingVisualsComponent : Component
+{
+ ///
+ /// A whitelist entities must match to be counted for collisions.
+ ///
+ [DataField]
+ public EntityWhitelist? Whitelist;
+
+ ///
+ /// The string to use for appearance data when no fixtures are being collided with.
+ ///
+ [DataField]
+ public string Default = "none";
+
+ ///
+ /// The list of fixtures to check for collisions, first one colliding is used so is most important.
+ ///
+ [DataField(required: true)]
+ public List Fixtures = new();
+
+ ///
+ /// Actively colliding fixtures.
+ ///
+ [DataField]
+ public HashSet Active = new();
+}
+
+[Serializable, NetSerializable]
+public enum CollidingVisuals : byte
+{
+ Layer,
+ Fixture
+}
diff --git a/Content.Shared/_DV/Physics/CollidingVisualsSystem.cs b/Content.Shared/_DV/Physics/CollidingVisualsSystem.cs
new file mode 100644
index 0000000000..a6b41c8476
--- /dev/null
+++ b/Content.Shared/_DV/Physics/CollidingVisualsSystem.cs
@@ -0,0 +1,64 @@
+using Content.Shared.Whitelist;
+using Robust.Shared.Physics.Events;
+
+namespace Content.Shared._DV.Physics;
+
+public sealed class CollidingVisualsSystem : EntitySystem
+{
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStartCollide);
+ SubscribeLocalEvent(OnEndCollide);
+ }
+
+ private void OnStartCollide(Entity ent, ref StartCollideEvent args)
+ {
+ if (_whitelist.IsWhitelistFail(ent.Comp.Whitelist, args.OtherEntity))
+ return;
+
+ // update active fixtures and state
+ var state = ent.Comp.Default;
+ foreach (var id in ent.Comp.Fixtures)
+ {
+ if (args.OurFixtureId == id)
+ {
+ ent.Comp.Active.Add(id);
+ state = id;
+ break;
+ }
+ }
+
+ SetState(ent, state);
+ }
+
+ private void OnEndCollide(Entity ent, ref EndCollideEvent args)
+ {
+ if (_whitelist.IsWhitelistFail(ent.Comp.Whitelist, args.OtherEntity))
+ return;
+
+ ent.Comp.Active.Remove(args.OurFixtureId);
+
+ // find the first state that is still active
+ var state = ent.Comp.Default;
+ foreach (var id in ent.Comp.Fixtures)
+ {
+ if (ent.Comp.Active.Contains(id))
+ {
+ state = id;
+ break;
+ }
+ }
+
+ SetState(ent, state);
+ }
+
+ public void SetState(EntityUid uid, string state)
+ {
+ _appearance.SetData(uid, CollidingVisuals.Fixture, state);
+ }
+}
diff --git a/Content.Shared/_DV/Whitelist/AiDetectableComponent.cs b/Content.Shared/_DV/Whitelist/AiDetectableComponent.cs
new file mode 100644
index 0000000000..2a07a98947
--- /dev/null
+++ b/Content.Shared/_DV/Whitelist/AiDetectableComponent.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._DV.Whitelist;
+
+///
+/// Component added to AI eye entity that lets it get detected by the syndicate ai detector.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class AiDetectableComponent : Component;
diff --git a/Resources/Locale/en-US/_DV/store/uplink/deception.ftl b/Resources/Locale/en-US/_DV/store/uplink/deception.ftl
new file mode 100644
index 0000000000..2bc05a77a6
--- /dev/null
+++ b/Resources/Locale/en-US/_DV/store/uplink/deception.ftl
@@ -0,0 +1,4 @@
+uplink-observation-kit-desc-deltav = Includes a syndicate crew monitor, high-capacity power cell and security hud disguised as sunglasses.
+
+uplink-ai-detector-name = AI Detector
+uplink-ai-detector-desc = This multitool has a custom display that changes color depending on how close the AI's camera is to you. Use it to stay clear of snooping eyes!
diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl
index 5d0bd7e33b..6c6a6e1108 100644
--- a/Resources/Locale/en-US/store/uplink-catalog.ftl
+++ b/Resources/Locale/en-US/store/uplink-catalog.ftl
@@ -210,7 +210,7 @@ uplink-micro-bomb-implanter-desc = Explode on death or manual activation with th
# Bundles
uplink-observation-kit-name = Observation Kit
-uplink-observation-kit-desc = Includes syndicate crew monitor, high power cell and security hud disguised as sunglasses.
+uplink-observation-kit-desc = Includes surveillance camera monitor board and security hud disguised as sunglasses.
uplink-emp-kit-name = Electrical Disruptor Kit
uplink-emp-kit-desc = The ultimate reversal on energy-based weaponry: Disables disablers, stuns stunbatons, discharges laser guns! Contains 3 EMP grenades and an EMP implanter. Note: Does not disrupt actual firearms.
diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml
index baa02a248c..920acbdb4c 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml
@@ -478,6 +478,7 @@
- state: ai_camera
shader: unshaded
map: ["base"]
+ - type: AiDetectable # DeltaV - AI detector
# The holographic representation of the AI that is projected from a holopad.
- type: entity
diff --git a/Resources/Prototypes/_DV/Catalog/Uplink/deception.yml b/Resources/Prototypes/_DV/Catalog/Uplink/deception.yml
new file mode 100644
index 0000000000..a80fcbcaf8
--- /dev/null
+++ b/Resources/Prototypes/_DV/Catalog/Uplink/deception.yml
@@ -0,0 +1,29 @@
+- type: listing
+ id: Observationkit
+ name: uplink-observation-kit-name
+ description: uplink-observation-kit-desc-deltav
+ icon:
+ sprite: Objects/Storage/boxicons.rsi
+ state: tracks
+ productEntity: Observationskit
+ discountCategory: usualDiscounts
+ discountDownTo:
+ Telecrystal: 3
+ cost:
+ Telecrystal: 6
+ categories:
+ - UplinkDeception
+
+- type: listing
+ id: MultitoolAiDetector
+ name: uplink-ai-detector-name
+ description: uplink-ai-detector-desc
+ icon:
+ sprite: Objects/Tools/multitool.rsi
+ state: icon
+ productEntity: MultitoolAiDetector
+ cost:
+ Telecrystal: 1
+ categories:
+ - UplinkDeception
+
diff --git a/Resources/Prototypes/_DV/Catalog/uplink_catalog.yml b/Resources/Prototypes/_DV/Catalog/uplink_catalog.yml
index 49151ee02a..f6a9107a36 100644
--- a/Resources/Prototypes/_DV/Catalog/uplink_catalog.yml
+++ b/Resources/Prototypes/_DV/Catalog/uplink_catalog.yml
@@ -77,20 +77,6 @@
components:
- SurplusBundle
-- type: listing
- id: Observationkit
- name: uplink-observation-kit-name
- description: uplink-observation-kit-desc
- icon: { sprite: /Textures/Objects/Storage/boxicons.rsi, state: tracks }
- productEntity: Observationskit
- discountCategory: usualDiscounts
- discountDownTo:
- Telecrystal: 3
- cost:
- Telecrystal: 6 # DeltaV - Price Goes up, but the goods are better!
- categories:
- - UplinkDeception
-
- type: listing
id: UplinkSyndicateRadioImplanter
name: uplink-syndicate-radio-implanter-name
diff --git a/Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml b/Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml
new file mode 100644
index 0000000000..6c3f2a04b3
--- /dev/null
+++ b/Resources/Prototypes/_DV/Entities/Objects/Tools/ai_detector.yml
@@ -0,0 +1,57 @@
+- type: entity
+ parent: Multitool
+ id: MultitoolAiDetector
+ suffix: AI Detector
+ components:
+ - type: Sprite
+ layers:
+ - state: icon
+ - state: green-unlit
+ shader: unshaded
+ map: [ enum.CollidingVisuals.Layer ]
+ - type: Physics
+ canCollide: true
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape: !type:PhysShapeAabb
+ bounds: "-0.25,-0.25,0.25,0.25"
+ density: 20
+ mask:
+ - ItemMask
+ restitution: 0.3 # fite me
+ friction: 0.2
+ yellow:
+ shape: !type:PhysShapeCircle
+ radius: 12
+ density: 0
+ hard: false
+ mask:
+ - GhostImpassable
+ red:
+ shape: !type:PhysShapeCircle
+ radius: 5
+ density: 0
+ hard: false
+ mask:
+ - GhostImpassable
+ - type: CollisionWake # don't stop checking for AI just because this isn't moving
+ enabled: false
+ - type: Appearance
+ - type: GenericVisualizer
+ visuals:
+ enum.CollidingVisuals.Fixture:
+ enum.CollidingVisuals.Layer:
+ none: { state: "green-unlit" }
+ yellow: { state: "yellow-unlit" }
+ red: { state: "red-unlit" }
+ - type: CollidingVisuals
+ whitelist:
+ components:
+ - AiDetectable
+ fixtures:
+ - red
+ - yellow
+ - type: MappingCategories # don't map valid multitool by mistake
+ categories:
+ - Syndicate