diff --git a/Content.Server/Damage/Components/DamagePopupComponent.cs b/Content.Server/Damage/Components/DamagePopupComponent.cs
new file mode 100644
index 0000000000..bbb9efd77c
--- /dev/null
+++ b/Content.Server/Damage/Components/DamagePopupComponent.cs
@@ -0,0 +1,20 @@
+using Content.Server.Damage.Systems;
+
+namespace Content.Server.Damage.Components;
+
+[RegisterComponent, Access(typeof(DamagePopupSystem))]
+public sealed class DamagePopupComponent : Component
+{
+ ///
+ /// Enum that will be used to determine the type of damage popup displayed.
+ ///
+ [DataField("damagePopupType")] [ViewVariables(VVAccess.ReadWrite)]
+ public DamagePopupType Type = DamagePopupType.Combined;
+}
+public enum DamagePopupType
+{
+ Combined,
+ Total,
+ Delta,
+ Hit,
+};
diff --git a/Content.Server/Damage/Systems/DamagePopupSystem.cs b/Content.Server/Damage/Systems/DamagePopupSystem.cs
new file mode 100644
index 0000000000..ba405e6393
--- /dev/null
+++ b/Content.Server/Damage/Systems/DamagePopupSystem.cs
@@ -0,0 +1,36 @@
+using Content.Server.Damage.Components;
+using Content.Server.Popups;
+using Content.Shared.Damage;
+using Robust.Shared.Player;
+
+namespace Content.Server.Damage.Systems;
+
+public sealed class DamagePopupSystem : EntitySystem
+{
+ [Dependency] private readonly PopupSystem _popupSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnDamageChange);
+ }
+
+ private void OnDamageChange(EntityUid uid, DamagePopupComponent component, DamageChangedEvent args)
+ {
+ if (args.DamageDelta != null)
+ {
+ var damageTotal = args.Damageable.TotalDamage;
+ var damageDelta = args.DamageDelta.Total;
+
+ var msg = component.Type switch
+ {
+ DamagePopupType.Delta => damageDelta.ToString(),
+ DamagePopupType.Total => damageTotal.ToString(),
+ DamagePopupType.Combined => damageDelta + " | " + damageTotal,
+ DamagePopupType.Hit => "!",
+ _ => "Invalid type",
+ };
+ _popupSystem.PopupEntity(msg, uid, Filter.Pvs(uid, 2F, EntityManager));
+ }
+ }
+}
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Security/target.yml b/Resources/Prototypes/Entities/Objects/Specific/Security/target.yml
new file mode 100644
index 0000000000..304a48258a
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Specific/Security/target.yml
@@ -0,0 +1,148 @@
+- type: entity
+ id: BaseTarget
+ parent: BaseStructureDynamic
+ abstract: true
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_stake
+ noRot: true
+ netsync: false
+ - type: Repairable
+ - type: DamagePopup
+ damagePopupType: Combined
+ - type: Fixtures
+ fixtures:
+ - shape:
+ !type:PhysShapeCircle
+ radius: 0.35
+ density: 200
+ mask:
+ - FullTileMask
+ layer:
+ - WallLayer
+ - type: InteractionOutline
+ - type: Physics
+ - type: Damageable
+ damageContainer: Inorganic
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 50
+ behaviors:
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ SheetSteel:
+ min: 5
+ max: 5
+ - !type:PlaySoundBehavior
+ sound:
+ path: /Audio/Effects/metalbreak.ogg
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
+
+- type: entity
+ name: human target
+ id: TargetHuman
+ parent: BaseTarget
+ description: A shooting target. This one is a human.
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_h
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 500
+ behaviors:
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ SheetSteel:
+ min: 10
+ max: 10
+ - !type:PlaySoundBehavior
+ sound:
+ path: /Audio/Effects/metalbreak.ogg
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
+
+- type: entity
+ name: syndicate target
+ id: TargetSyndicate
+ parent: BaseTarget
+ description: A shooting target. This one is a syndicate agent.
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_s
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 500
+ behaviors:
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ SheetSteel:
+ min: 10
+ max: 10
+ - !type:PlaySoundBehavior
+ sound:
+ path: /Audio/Effects/metalbreak.ogg
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
+
+- type: entity
+ name: clown target
+ id: TargetClown
+ parent: BaseTarget
+ description: A shooting target. This one is a clown.
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_c
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 500
+ behaviors:
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ SheetSteel:
+ min: 10
+ max: 10
+ - !type:PlaySoundBehavior
+ sound:
+ path: /Audio/Effects/metalbreak.ogg
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
+
+# put it on a salvage or something
+- type: entity
+ name: strange target
+ id: TargetStrange
+ parent: BaseTarget
+ description: A shooting target. You aren't quite sure what this one is, but it seems to be extra robust.
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_f
+ - type: Destructible
+ thresholds:
+ - trigger:
+ !type:DamageTrigger
+ damage: 2000
+ behaviors:
+ - !type:SpawnEntitiesBehavior
+ spawn:
+ SheetSteel:
+ min: 10
+ max: 10
+ - !type:PlaySoundBehavior
+ sound:
+ path: /Audio/Effects/metalbreak.ogg
+ - !type:DoActsBehavior
+ acts: [ "Destruction" ]
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
index 79cd9fc79f..1bc5266edc 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
@@ -115,6 +115,16 @@
proto: RedLaser
fireCost: 62.5
+- type: entity
+ name: practice laser gun
+ parent: WeaponLaserCarbine
+ id: WeaponLaserCarbinePractice
+ description: A modified version of the basic laser gun, this one fires less concentrated energy bolts designed for target practice.
+ components:
+ - type: HitscanBatteryAmmoProvider
+ proto: RedLaserPractice
+ fireCost: 62.5
+
- type: entity
name: pulse pistol
parent: BaseWeaponBatterySmall
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml
index ff142824d7..c3349e2648 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml
@@ -30,6 +30,21 @@
sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
state: impact_laser
+- type: hitscan
+ id: RedLaserPractice
+ damage:
+ types:
+ Heat: 0
+ muzzleFlash:
+ sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
+ state: muzzle_laser
+ travelFlash:
+ sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
+ state: beam
+ impactFlash:
+ sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi
+ state: impact_laser
+
- type: hitscan
id: RedMediumLaser
damage:
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index a5880d4da2..4569a642f1 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -347,6 +347,9 @@
- CartridgeCaselessRifleRubber
- CartridgeLightRifleRubber
- CartridgeRifleRubber
+ - TargetHuman
+ - TargetSyndicate
+ - TargetClown
- type: MaterialStorage
whitelist:
tags:
diff --git a/Resources/Prototypes/Recipes/Lathes/security.yml b/Resources/Prototypes/Recipes/Lathes/security.yml
index 8002dd1a3a..f59cdd19c4 100644
--- a/Resources/Prototypes/Recipes/Lathes/security.yml
+++ b/Resources/Prototypes/Recipes/Lathes/security.yml
@@ -170,3 +170,33 @@
Plastic: 15
Steel: 10
Glass: 5
+
+- type: latheRecipe
+ id: TargetHuman
+ icon:
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_h
+ result: TargetHuman
+ completetime: 5
+ materials:
+ Steel: 10
+
+- type: latheRecipe
+ id: TargetClown
+ icon:
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_c
+ result: TargetClown
+ completetime: 5
+ materials:
+ Steel: 10
+
+- type: latheRecipe
+ id: TargetSyndicate
+ icon:
+ sprite: Objects/Specific/Security/target.rsi
+ state: target_s
+ result: TargetSyndicate
+ completetime: 5
+ materials:
+ Steel: 10
diff --git a/Resources/Textures/Objects/Specific/Security/target.rsi/meta.json b/Resources/Textures/Objects/Specific/Security/target.rsi/meta.json
new file mode 100644
index 0000000000..de64cfcf1f
--- /dev/null
+++ b/Resources/Textures/Objects/Specific/Security/target.rsi/meta.json
@@ -0,0 +1,26 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/a0f470046f01c2e8643d27e8dac8b9cb08b9a68d/icons/obj/objects.dmi",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "target_stake"
+ },
+ {
+ "name": "target_h"
+ },
+ {
+ "name": "target_s"
+ },
+ {
+ "name": "target_c"
+ },
+ {
+ "name": "target_f"
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Specific/Security/target.rsi/target_c.png b/Resources/Textures/Objects/Specific/Security/target.rsi/target_c.png
new file mode 100644
index 0000000000..6a1105d5a2
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Security/target.rsi/target_c.png differ
diff --git a/Resources/Textures/Objects/Specific/Security/target.rsi/target_f.png b/Resources/Textures/Objects/Specific/Security/target.rsi/target_f.png
new file mode 100644
index 0000000000..2ab0416452
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Security/target.rsi/target_f.png differ
diff --git a/Resources/Textures/Objects/Specific/Security/target.rsi/target_h.png b/Resources/Textures/Objects/Specific/Security/target.rsi/target_h.png
new file mode 100644
index 0000000000..bb57c444cf
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Security/target.rsi/target_h.png differ
diff --git a/Resources/Textures/Objects/Specific/Security/target.rsi/target_s.png b/Resources/Textures/Objects/Specific/Security/target.rsi/target_s.png
new file mode 100644
index 0000000000..149e686866
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Security/target.rsi/target_s.png differ
diff --git a/Resources/Textures/Objects/Specific/Security/target.rsi/target_stake.png b/Resources/Textures/Objects/Specific/Security/target.rsi/target_stake.png
new file mode 100644
index 0000000000..deb22137d1
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Security/target.rsi/target_stake.png differ