diff --git a/Content.Server/Emp/EmpOnTriggerComponent.cs b/Content.Server/Emp/EmpOnTriggerComponent.cs
new file mode 100644
index 0000000000..b3f486b895
--- /dev/null
+++ b/Content.Server/Emp/EmpOnTriggerComponent.cs
@@ -0,0 +1,17 @@
+namespace Content.Server.Emp;
+
+///
+/// Upon being triggered will EMP area around it.
+///
+[RegisterComponent]
+sealed class EmpOnTriggerComponent : Component
+{
+ [DataField("range"), ViewVariables(VVAccess.ReadWrite)]
+ public float Range = 1.0f;
+
+ ///
+ /// How much energy will be consumed per battery in range
+ ///
+ [DataField("energyConsumption"), ViewVariables(VVAccess.ReadWrite)]
+ public float EnergyConsumption;
+}
diff --git a/Content.Server/Emp/EmpSystem.cs b/Content.Server/Emp/EmpSystem.cs
new file mode 100644
index 0000000000..865f6a6947
--- /dev/null
+++ b/Content.Server/Emp/EmpSystem.cs
@@ -0,0 +1,39 @@
+using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.Map;
+
+namespace Content.Server.Emp;
+
+public sealed class EmpSystem : EntitySystem
+{
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+ public const string EmpPulseEffectPrototype = "EffectEmpPulse";
+ public const string EmpDisabledEffectPrototype = "EffectEmpDisabled";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(HandleEmpTrigger);
+ }
+
+ public void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption)
+ {
+ foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
+ {
+ var ev = new EmpPulseEvent(energyConsumption, false);
+ RaiseLocalEvent(uid, ref ev);
+ if (ev.Affected)
+ Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates);
+ }
+ Spawn(EmpPulseEffectPrototype, coordinates);
+ }
+
+ private void HandleEmpTrigger(EntityUid uid, EmpOnTriggerComponent comp, TriggerEvent args)
+ {
+ EmpPulse(Transform(uid).Coordinates.ToMap(EntityManager), comp.Range, comp.EnergyConsumption);
+ args.Handled = true;
+ }
+}
+
+[ByRefEvent]
+public record struct EmpPulseEvent(float EnergyConsumption, bool Affected);
diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs
index bef74772a7..545ad4d4f3 100644
--- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs
+++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs
@@ -22,6 +22,7 @@ using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Content.Shared.DoAfter;
+using Content.Server.Emp;
namespace Content.Server.Light.EntitySystems
{
@@ -63,6 +64,8 @@ namespace Content.Server.Light.EntitySystems
SubscribeLocalEvent(OnPowerChanged);
SubscribeLocalEvent(OnDoAfter);
+
+ SubscribeLocalEvent(OnEmpPulse);
}
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
@@ -421,5 +424,11 @@ namespace Content.Server.Light.EntitySystems
args.Handled = true;
}
+
+ private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
+ {
+ args.Affected = true;
+ TryDestroyBulb(uid, component);
+ }
}
}
diff --git a/Content.Server/Power/EntitySystems/ApcSystem.cs b/Content.Server/Power/EntitySystems/ApcSystem.cs
index 8ce9d26ec0..d1333759bf 100644
--- a/Content.Server/Power/EntitySystems/ApcSystem.cs
+++ b/Content.Server/Power/EntitySystems/ApcSystem.cs
@@ -1,3 +1,4 @@
+using Content.Server.Emp;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Shared.Access.Components;
@@ -44,6 +45,8 @@ namespace Content.Server.Power.EntitySystems
SubscribeLocalEvent(OnToolFinished);
SubscribeLocalEvent(OnInteractUsing);
SubscribeLocalEvent(OnExamine);
+
+ SubscribeLocalEvent(OnEmpPulse);
}
// Change the APC's state only when the battery state changes, or when it's first created.
@@ -247,5 +250,14 @@ namespace Content.Server.Power.EntitySystems
? "apc-component-on-examine-panel-open"
: "apc-component-on-examine-panel-closed"));
}
+
+ private void OnEmpPulse(EntityUid uid, ApcComponent component, ref EmpPulseEvent args)
+ {
+ if (component.MainBreakerEnabled)
+ {
+ args.Affected = true;
+ ApcToggleBreaker(uid, component);
+ }
+ }
}
}
diff --git a/Content.Server/Power/EntitySystems/BatterySystem.cs b/Content.Server/Power/EntitySystems/BatterySystem.cs
index a46db7dcb9..b8dc676485 100644
--- a/Content.Server/Power/EntitySystems/BatterySystem.cs
+++ b/Content.Server/Power/EntitySystems/BatterySystem.cs
@@ -1,4 +1,5 @@
using Content.Server.Cargo.Systems;
+using Content.Server.Emp;
using Content.Server.Power.Components;
using Content.Shared.Examine;
using Content.Shared.Rejuvenate;
@@ -17,6 +18,7 @@ namespace Content.Server.Power.EntitySystems
SubscribeLocalEvent(OnNetBatteryRejuvenate);
SubscribeLocalEvent(OnBatteryRejuvenate);
SubscribeLocalEvent(CalculateBatteryPrice);
+ SubscribeLocalEvent(OnEmpPulse);
SubscribeLocalEvent(PreSync);
SubscribeLocalEvent(PostSync);
@@ -87,5 +89,11 @@ namespace Content.Server.Power.EntitySystems
{
args.Price += component.CurrentCharge * component.PricePerJoule;
}
+
+ private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args)
+ {
+ args.Affected = true;
+ component.UseCharge(args.EnergyConsumption);
+ }
}
}
diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl
index 71de41859a..12d15201ab 100644
--- a/Resources/Locale/en-US/store/uplink-catalog.ftl
+++ b/Resources/Locale/en-US/store/uplink-catalog.ftl
@@ -39,6 +39,9 @@ uplink-c4-desc = Use it to breach walls, airlocks or sabotage equipment. It can
uplink-c4-bundle-name = C-4 bundle
uplink-c4-bundle-desc = Because sometimes quantity is quality. Contains 8 C-4 plastic explosives.
+uplink-emp-grenade-name = Emp Grenade
+uplink-emp-grenade-desc = Releases electromagnetic pulses that disrupt or damage many electronic devices or drain power cells.
+
# Ammo
uplink-pistol-magazine-name = Pistol Magazine (.35 auto)
uplink-pistol-magazine-desc = Pistol magazine with 10 catridges. Compatible with Viper.
diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml
index 2265e019fc..ba23f6988d 100644
--- a/Resources/Prototypes/Catalog/uplink_catalog.yml
+++ b/Resources/Prototypes/Catalog/uplink_catalog.yml
@@ -142,6 +142,16 @@
categories:
- UplinkExplosives
+- type: listing
+ id: UplinkEmpGrenade
+ name: uplink-emp-grenade-name
+ description: uplink-emp-grenade-desc
+ productEntity: EmpGrenade
+ cost:
+ Telecrystal: 4
+ categories:
+ - UplinkExplosives
+
# Ammo
- type: listing
diff --git a/Resources/Prototypes/Entities/Effects/emp_effects.yml b/Resources/Prototypes/Entities/Effects/emp_effects.yml
new file mode 100644
index 0000000000..c04b765dec
--- /dev/null
+++ b/Resources/Prototypes/Entities/Effects/emp_effects.yml
@@ -0,0 +1,44 @@
+- type: entity
+ id: EffectEmpPulse
+ noSpawn: true
+ components:
+ - type: TimedDespawn
+ lifetime: 0.8
+ - type: Sprite
+ netsync: false
+ drawdepth: Effects
+ noRot: true
+ layers:
+ - shader: unshaded
+ map: ["enum.EffectLayers.Unshaded"]
+ sprite: Effects/emp.rsi
+ state: emp_pulse
+ - type: EffectVisuals
+ - type: Tag
+ tags:
+ - HideContextMenu
+ - type: EmitSoundOnSpawn
+ sound:
+ path: /Audio/Effects/Lightning/lightningbolt.ogg
+ - type: AnimationPlayer
+
+- type: entity
+ id: EffectEmpDisabled
+ noSpawn: true
+ components:
+ - type: TimedDespawn
+ lifetime: 0.4
+ - type: Sprite
+ netsync: false
+ drawdepth: Effects
+ noRot: true
+ layers:
+ - shader: unshaded
+ map: ["enum.EffectLayers.Unshaded"]
+ sprite: Effects/emp.rsi
+ state: emp_disable
+ - type: EffectVisuals
+ - type: Tag
+ tags:
+ - HideContextMenu
+ - type: AnimationPlayer
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
index 016860ea27..a980fa956a 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
@@ -154,3 +154,21 @@
enum.Trigger.TriggerVisualState.Unprimed: complete
- type: StaticPrice
price: 25
+
+- type: entity
+ name: emp grenade
+ description: Releases electromagnetic pulses that disrupt or damage many electronic devices or drain power cells.
+ parent: GrenadeBase
+ id: EmpGrenade
+ components:
+ - type: Sprite
+ sprite: Objects/Weapons/Grenades/empgrenade.rsi
+ - type: EmpOnTrigger
+ range: 4
+ energyConsumption: 50000
+ - type: DeleteOnTrigger
+ - type: Appearance
+ visuals:
+ - type: TimerTriggerVisualizer
+ countdown_sound:
+ path: /Audio/Effects/countdown.ogg
diff --git a/Resources/Textures/Effects/emp.rsi/emp_disable.png b/Resources/Textures/Effects/emp.rsi/emp_disable.png
new file mode 100644
index 0000000000..fe45eabc71
Binary files /dev/null and b/Resources/Textures/Effects/emp.rsi/emp_disable.png differ
diff --git a/Resources/Textures/Effects/emp.rsi/emp_pulse.png b/Resources/Textures/Effects/emp.rsi/emp_pulse.png
new file mode 100644
index 0000000000..49f4be26ac
Binary files /dev/null and b/Resources/Textures/Effects/emp.rsi/emp_pulse.png differ
diff --git a/Resources/Textures/Effects/emp.rsi/meta.json b/Resources/Textures/Effects/emp.rsi/meta.json
new file mode 100644
index 0000000000..378759c5b4
--- /dev/null
+++ b/Resources/Textures/Effects/emp.rsi/meta.json
@@ -0,0 +1,56 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e52683d3872347af447bb0ff74c420d4a4e91ea8",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "emp_disable",
+ "directions": 4,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "emp_pulse",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/equipped-BELT.png b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/equipped-BELT.png
new file mode 100644
index 0000000000..c8d813b753
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/equipped-BELT.png differ
diff --git a/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/icon.png b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/icon.png
new file mode 100644
index 0000000000..5f43e51ee0
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/meta.json b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/meta.json
new file mode 100644
index 0000000000..889ac5cb4c
--- /dev/null
+++ b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/meta.json
@@ -0,0 +1,27 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e52683d3872347af447bb0ff74c420d4a4e91ea8",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ },
+ {
+ "name": "primed",
+ "delays": [
+ [
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "equipped-BELT",
+ "directions": 4
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/primed.png b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/primed.png
new file mode 100644
index 0000000000..a11f70171f
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Grenades/empgrenade.rsi/primed.png differ