]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Create Generic DamageOnInteract/Attacked Comps/Systems (#30244)
authorPlykiya <58439124+Plykiya@users.noreply.github.com>
Fri, 9 Aug 2024 05:32:41 +0000 (22:32 -0700)
committerGitHub <noreply@github.com>
Fri, 9 Aug 2024 05:32:41 +0000 (15:32 +1000)
* Everything but the submodule

* stuff I forgot

* heat

* missed lights

* behonky

* LocId

* I guess it was a skill issue?

* predicted audio

* It works with lights now

* Borg equality

* Gorilla gauntlet grants protection from anomaly returned damage when attacking it

* woops, there we go

* NONE

* Use DamageModifierSets, remove Behonker damage

* Reviews dealt with

---------

Co-authored-by: plykiya <plykiya@protonmail.com>
25 files changed:
Content.Server/Clothing/Components/GloveHeatResistanceComponent.cs [deleted file]
Content.Server/Light/Components/PoweredLightComponent.cs
Content.Server/Light/EntitySystems/PoweredLightSystem.cs
Content.Shared/Anomaly/Components/AnomalyComponent.cs
Content.Shared/Anomaly/SharedAnomalySystem.cs
Content.Shared/Damage/Components/DamageOnAttackedComponent.cs [new file with mode: 0644]
Content.Shared/Damage/Components/DamageOnAttackedProtectionComponent.cs [new file with mode: 0644]
Content.Shared/Damage/Components/DamageOnInteractComponent.cs [new file with mode: 0644]
Content.Shared/Damage/Components/DamageOnInteractProtectionComponent.cs [new file with mode: 0644]
Content.Shared/Damage/Systems/DamageOnAttackedSystem.cs [new file with mode: 0644]
Content.Shared/Damage/Systems/DamageOnInteractSystem.cs [new file with mode: 0644]
Content.Shared/Inventory/InventorySystem.Slots.cs
Resources/Prototypes/Entities/Clothing/Hands/base_clothinghands.yml
Resources/Prototypes/Entities/Clothing/Hands/colored.yml
Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml
Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml
Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml
Resources/Prototypes/Entities/Structures/Lighting/strobe_lighting.yml
Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml
Resources/Prototypes/Entities/Structures/Wallmounts/service_light.yml

diff --git a/Content.Server/Clothing/Components/GloveHeatResistanceComponent.cs b/Content.Server/Clothing/Components/GloveHeatResistanceComponent.cs
deleted file mode 100644 (file)
index 29a3b90..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace Content.Server.Clothing.Components;
-
-/// <summary>
-///     TODO this needs removed somehow.
-///     Handles 'heat resistance' for gloves touching bulbs and that's it, ick.
-/// </summary>
-[RegisterComponent]
-public sealed partial class GloveHeatResistanceComponent : Component
-{
-    [DataField("heatResistance")]
-    public int HeatResistance = 323;
-}
index 489a49eec2202d7f855841d5d85fc2002a9d09a4..1a6f610516e815a1c1ff68df451fac203578f821 100644 (file)
@@ -30,10 +30,6 @@ namespace Content.Server.Light.Components
         [DataField("on")]
         public bool On = true;
 
-        [DataField("damage", required: true)]
-        [ViewVariables(VVAccess.ReadWrite)]
-        public DamageSpecifier Damage = default!;
-
         [DataField("ignoreGhostsBoo")]
         public bool IgnoreGhostsBoo;
 
index 8aa432c21ea22ac2994e03416a923552433a8afd..33b7ce0782f4a4d2b01b16380820ec6fd7e7e4e6 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Administration.Logs;
-using Content.Server.Clothing.Components;
 using Content.Server.DeviceLinking.Events;
 using Content.Server.DeviceLinking.Systems;
 using Content.Server.DeviceNetwork;
@@ -24,6 +23,8 @@ using Robust.Shared.Containers;
 using Robust.Shared.Player;
 using Robust.Shared.Timing;
 using Robust.Shared.Audio.Systems;
+using Content.Shared.Damage.Systems;
+using Content.Shared.Damage.Components;
 
 namespace Content.Server.Light.EntitySystems
 {
@@ -33,11 +34,8 @@ namespace Content.Server.Light.EntitySystems
     public sealed class PoweredLightSystem : EntitySystem
     {
         [Dependency] private readonly IGameTiming _gameTiming = default!;
-        [Dependency] private readonly DamageableSystem _damageableSystem = default!;
         [Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!;
         [Dependency] private readonly LightBulbSystem _bulbSystem = default!;
-        [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
-        [Dependency] private readonly IAdminLogManager _adminLogger= default!;
         [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
         [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
         [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
@@ -45,7 +43,7 @@ namespace Content.Server.Light.EntitySystems
         [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly PointLightSystem _pointLight = default!;
         [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-        [Dependency] private readonly InventorySystem _inventory = default!;
+        [Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!;
 
         private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
         public const string LightBulbContainer = "light_bulb";
@@ -106,40 +104,7 @@ namespace Content.Server.Light.EntitySystems
             if (bulbUid == null)
                 return;
 
-            // check if it's possible to apply burn damage to user
             var userUid = args.User;
-            if (EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
-            {
-                // get users heat resistance
-                var res = int.MinValue;
-                if (_inventory.TryGetSlotEntity(userUid, "gloves", out var slotEntity) &&
-                    TryComp<GloveHeatResistanceComponent>(slotEntity, out var gloves))
-                {
-                    res = gloves.HeatResistance;
-                }
-
-                // check heat resistance against user
-                var burnedHand = light.CurrentLit && res < lightBulb.BurningTemperature;
-                if (burnedHand)
-                {
-                    var damage = _damageableSystem.TryChangeDamage(userUid, light.Damage, origin: userUid);
-
-                    // If damage is null then the entity could not take heat damage so they did not get burned.
-                    if (damage != null)
-                    {
-
-                        var burnMsg = Loc.GetString("powered-light-component-burn-hand");
-                        _popupSystem.PopupEntity(burnMsg, uid, userUid);
-                        _adminLogger.Add(LogType.Damaged, $"{ToPrettyString(args.User):user} burned their hand on {ToPrettyString(args.Target):target} and received {damage.GetTotal():damage} damage");
-                        _audio.PlayEntity(light.BurnHandSound, Filter.Pvs(uid), uid, true);
-
-                        args.Handled = true;
-                        return;
-                    }
-                }
-            }
-
-
             //removing a broken/burned bulb, so allow instant removal
             if(TryComp<LightBulbComponent>(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal)
             {
@@ -435,6 +400,10 @@ namespace Content.Server.Light.EntitySystems
                 if (softness != null)
                     _pointLight.SetSoftness(uid, (float) softness, pointLight);
             }
+
+            // light bulbs burn your hands!
+            if (TryComp<DamageOnInteractComponent>(uid, out var damageOnInteractComp))
+                _damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value);
         }
 
         public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null)
index 88e942ec507914696afd8cb2d5b4b3b1fbf64d12..724dfd38d2f6609314e3e87a65a0be365ca7eddc 100644 (file)
@@ -202,20 +202,6 @@ public sealed partial class AnomalyComponent : Component
     public float GrowingPointMultiplier = 1.5f;
     #endregion
 
-    /// <summary>
-    /// The amount of damage dealt when either a player touches the anomaly
-    /// directly or by hitting the anomaly.
-    /// </summary>
-    [DataField(required: true)]
-    public DamageSpecifier AnomalyContactDamage = default!;
-
-    /// <summary>
-    /// The sound effect played when a player
-    /// burns themselves on an anomaly via contact.
-    /// </summary>
-    [DataField]
-    public SoundSpecifier AnomalyContactDamageSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
-
     /// <summary>
     /// A prototype entity that appears when an anomaly supercrit collapse.
     /// </summary>
index da1d31c6ff656d6a5a432fd63116d47b9727417f..c3d6591b7252de8e63c8954b1b025725128cd8af 100644 (file)
@@ -30,7 +30,6 @@ public abstract class SharedAnomalySystem : EntitySystem
     [Dependency] private readonly INetManager _net = default!;
     [Dependency] protected readonly IRobustRandom Random = default!;
     [Dependency] protected readonly ISharedAdminLogManager AdminLog = default!;
-    [Dependency] private readonly DamageableSystem _damageable = default!;
     [Dependency] protected readonly SharedAudioSystem Audio = default!;
     [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
     [Dependency] private readonly SharedPhysicsSystem _physics = default!;
@@ -42,26 +41,10 @@ public abstract class SharedAnomalySystem : EntitySystem
     {
         base.Initialize();
 
-        SubscribeLocalEvent<AnomalyComponent, InteractHandEvent>(OnInteractHand);
-        SubscribeLocalEvent<AnomalyComponent, AttackedEvent>(OnAttacked);
         SubscribeLocalEvent<AnomalyComponent, MeleeThrowOnHitStartEvent>(OnAnomalyThrowStart);
         SubscribeLocalEvent<AnomalyComponent, MeleeThrowOnHitEndEvent>(OnAnomalyThrowEnd);
     }
 
-    private void OnInteractHand(EntityUid uid, AnomalyComponent component, InteractHandEvent args)
-    {
-        DoAnomalyBurnDamage(uid, args.User, component);
-        args.Handled = true;
-    }
-
-    private void OnAttacked(EntityUid uid, AnomalyComponent component, AttackedEvent args)
-    {
-        if (HasComp<CorePoweredThrowerComponent>(args.Used))
-            return;
-
-        DoAnomalyBurnDamage(uid, args.User, component);
-    }
-
     private void OnAnomalyThrowStart(Entity<AnomalyComponent> ent, ref MeleeThrowOnHitStartEvent args)
     {
         if (!TryComp<CorePoweredThrowerComponent>(args.Used, out var corePowered) || !TryComp<PhysicsComponent>(ent, out var body))
@@ -75,15 +58,6 @@ public abstract class SharedAnomalySystem : EntitySystem
         _physics.SetBodyType(ent, BodyType.Static);
     }
 
-    public void DoAnomalyBurnDamage(EntityUid source, EntityUid target, AnomalyComponent component)
-    {
-        _damageable.TryChangeDamage(target, component.AnomalyContactDamage, true);
-        if (!Timing.IsFirstTimePredicted || _net.IsServer)
-            return;
-        Audio.PlayPvs(component.AnomalyContactDamageSound, source);
-        Popup.PopupEntity(Loc.GetString("anomaly-component-contact-damage"), target, target);
-    }
-
     public void DoAnomalyPulse(EntityUid uid, AnomalyComponent? component = null)
     {
         if (!Resolve(uid, ref component))
diff --git a/Content.Shared/Damage/Components/DamageOnAttackedComponent.cs b/Content.Shared/Damage/Components/DamageOnAttackedComponent.cs
new file mode 100644 (file)
index 0000000..5f6bd7d
--- /dev/null
@@ -0,0 +1,47 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Damage.Components;
+
+
+/// <summary>
+/// This component is added to entities that you want to damage the player
+/// if the player interacts with it. For example, if a player tries touching
+/// a hot light bulb or an anomaly. This damage can be cancelled if the user
+/// has a component that protects them from this.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class DamageOnAttackedComponent : Component
+{
+    /// <summary>
+    /// How much damage to apply to the person making contact
+    /// </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public DamageSpecifier Damage = default!;
+
+    /// <summary>
+    /// Whether the damage should be resisted by a person's armor values
+    /// and the <see cref="DamageOnAttackedProtectionComponent"/>
+    /// </summary>
+    [DataField]
+    public bool IgnoreResistances = false;
+
+    /// <summary>
+    /// What kind of localized text should pop up when they interact with the entity
+    /// </summary>
+    [DataField]
+    public LocId? PopupText;
+
+    /// <summary>
+    /// The sound that should be made when interacting with the entity
+    /// </summary>
+    [DataField]
+    public SoundSpecifier InteractSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
+
+    /// <summary>
+    /// Generic boolean to toggle the damage application on and off
+    /// This is useful for things that can be toggled on or off, like a stovetop
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool IsDamageActive = true;
+}
diff --git a/Content.Shared/Damage/Components/DamageOnAttackedProtectionComponent.cs b/Content.Shared/Damage/Components/DamageOnAttackedProtectionComponent.cs
new file mode 100644 (file)
index 0000000..9581d20
--- /dev/null
@@ -0,0 +1,29 @@
+using Content.Shared.Inventory;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Damage.Components;
+
+
+/// <summary>
+/// This component is added to entities to protect them from being damaged
+/// when attacking objects with the <see cref="DamageOnAttackedComponent"/>
+/// If the entity has sufficient protection, the entity will take no damage.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class DamageOnAttackedProtectionComponent : Component, IClothingSlots
+{
+    /// <summary>
+    /// How much and what kind of damage to protect the user from
+    /// when interacting with something with <see cref="DamageOnInteractComponent"/>
+    /// </summary>
+    [DataField(required: true)]
+    public DamageModifierSet DamageProtection = default!;
+
+    /// <summary>
+    /// Only protects if the item is in the correct slot
+    /// i.e. having gloves in your pocket doesn't protect you, it has to be on your hands
+    /// Set slots to NONE if it works while you hold the item in your main hand
+    /// </summary>
+    [DataField]
+    public SlotFlags Slots { get; set; } = SlotFlags.WITHOUT_POCKET;
+}
diff --git a/Content.Shared/Damage/Components/DamageOnInteractComponent.cs b/Content.Shared/Damage/Components/DamageOnInteractComponent.cs
new file mode 100644 (file)
index 0000000..9487dec
--- /dev/null
@@ -0,0 +1,47 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Damage.Components;
+
+
+/// <summary>
+/// This component is added to entities that you want to damage the player
+/// if the player interacts with it. For example, if a player tries touching
+/// a hot light bulb or an anomaly. This damage can be cancelled if the user
+/// has a component that protects them from this.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class DamageOnInteractComponent : Component
+{
+    /// <summary>
+    /// How much damage to apply to the person making contact
+    /// </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public DamageSpecifier Damage = default!;
+
+    /// <summary>
+    /// Whether the damage should be resisted by a person's armor values
+    /// and the <see cref="DamageOnInteractProtectionComponent"/>
+    /// </summary>
+    [DataField]
+    public bool IgnoreResistances;
+
+    /// <summary>
+    /// What kind of localized text should pop up when they interact with the entity
+    /// </summary>
+    [DataField]
+    public LocId? PopupText;
+
+    /// <summary>
+    /// The sound that should be made when interacting with the entity
+    /// </summary>
+    [DataField]
+    public SoundSpecifier InteractSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
+
+    /// <summary>
+    /// Generic boolean to toggle the damage application on and off
+    /// This is useful for things that can be toggled on or off, like a stovetop
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool IsDamageActive = true;
+}
diff --git a/Content.Shared/Damage/Components/DamageOnInteractProtectionComponent.cs b/Content.Shared/Damage/Components/DamageOnInteractProtectionComponent.cs
new file mode 100644 (file)
index 0000000..f57c3d2
--- /dev/null
@@ -0,0 +1,29 @@
+using Content.Shared.Inventory;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Damage.Components;
+
+
+/// <summary>
+/// This component is added to entities to protect them from being damaged
+/// when interacting with objects with the <see cref="DamageOnInteractComponent"/>
+/// If the entity has sufficient protection, interaction with the object is not cancelled.
+/// This allows the user to do things like remove a lightbulb.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class DamageOnInteractProtectionComponent : Component, IClothingSlots
+{
+    /// <summary>
+    /// How much and what kind of damage to protect the user from
+    /// when interacting with something with <see cref="DamageOnInteractComponent"/>
+    /// </summary>
+    [DataField(required: true)]
+    public DamageModifierSet DamageProtection = default!;
+
+    /// <summary>
+    /// Only protects if the item is in the correct slot
+    /// i.e. having gloves in your pocket doesn't protect you, it has to be on your hands
+    /// </summary>
+    [DataField]
+    public SlotFlags Slots { get; set; } = SlotFlags.GLOVES;
+}
diff --git a/Content.Shared/Damage/Systems/DamageOnAttackedSystem.cs b/Content.Shared/Damage/Systems/DamageOnAttackedSystem.cs
new file mode 100644 (file)
index 0000000..ab5866c
--- /dev/null
@@ -0,0 +1,99 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Damage.Components;
+using Content.Shared.Database;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Inventory;
+using Content.Shared.Popups;
+using Content.Shared.Weapons.Melee.Events;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Damage.Systems;
+
+public sealed class DamageOnAttackedSystem : EntitySystem
+{
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+    [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly InventorySystem _inventorySystem = default!;
+    [Dependency] private readonly SharedHandsSystem _handsSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DamageOnAttackedComponent, AttackedEvent>(OnAttacked);
+    }
+
+    /// <summary>
+    /// Damages the user that attacks the entity and potentially
+    /// plays a sound or pops up text in response
+    /// </summary>
+    /// <param name="entity">The entity being hit</param>
+    /// <param name="args">Contains the user that hit the entity</param>
+    private void OnAttacked(Entity<DamageOnAttackedComponent> entity, ref AttackedEvent args)
+    {
+        if (!entity.Comp.IsDamageActive)
+            return;
+
+        var totalDamage = entity.Comp.Damage;
+
+        if (!entity.Comp.IgnoreResistances)
+        {
+            // try to get the damage on attacked protection component from something the entity has in their inventory
+            _inventorySystem.TryGetInventoryEntity<DamageOnAttackedProtectionComponent>(args.User, out var protectiveEntity);
+
+            // if comp is null that means the user didn't have anything equipped that protected them
+            // let's check their hands to see if the thing they attacked with gives them protection, like the GORILLA gauntlet
+            if (protectiveEntity.Comp == null && TryComp<HandsComponent>(args.User, out var handsComp))
+            {
+                if (_handsSystem.TryGetActiveItem((args.User, handsComp), out var itemInHand) &&
+                    TryComp<DamageOnAttackedProtectionComponent>(itemInHand, out var itemProtectComp)
+                    && itemProtectComp.Slots == SlotFlags.NONE)
+                {
+                    protectiveEntity = (itemInHand.Value, itemProtectComp);
+                }
+            }
+
+            // if comp is null, that means both the inventory and hands had nothing to protect them
+            // let's check if the entity itself has the protective comp, like with borgs
+            if (protectiveEntity.Comp == null &&
+                TryComp<DamageOnAttackedProtectionComponent>(args.User, out var protectiveComp))
+            {
+                protectiveEntity = (args.User, protectiveComp);
+            }
+
+            // if comp is NOT NULL that means they have damage protection!
+            if (protectiveEntity.Comp != null)
+            {
+                totalDamage = DamageSpecifier.ApplyModifierSet(totalDamage, protectiveEntity.Comp.DamageProtection);
+            }
+        }
+
+        totalDamage = _damageableSystem.TryChangeDamage(args.User, totalDamage, entity.Comp.IgnoreResistances, origin: entity);
+
+        if (totalDamage != null && totalDamage.AnyPositive())
+        {
+            _adminLogger.Add(LogType.Damaged, $"{ToPrettyString(args.User):user} injured themselves by attacking {ToPrettyString(entity):target} and received {totalDamage.GetTotal():damage} damage");
+            _audioSystem.PlayPredicted(entity.Comp.InteractSound, entity, args.User);
+
+            if (entity.Comp.PopupText != null)
+                _popupSystem.PopupClient(Loc.GetString(entity.Comp.PopupText), args.User, args.User);
+
+        }
+    }
+
+    public void SetIsDamageActiveTo(Entity<DamageOnAttackedComponent> entity, bool mode)
+    {
+        if (entity.Comp.IsDamageActive == mode)
+            return;
+
+        entity.Comp.IsDamageActive = mode;
+        Dirty(entity);
+    }
+}
diff --git a/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs b/Content.Shared/Damage/Systems/DamageOnInteractSystem.cs
new file mode 100644 (file)
index 0000000..4b50a1f
--- /dev/null
@@ -0,0 +1,85 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Damage.Components;
+using Content.Shared.Database;
+using Content.Shared.Interaction;
+using Content.Shared.Inventory;
+using Content.Shared.Popups;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Damage.Systems;
+
+public sealed class DamageOnInteractSystem : EntitySystem
+{
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+    [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly InventorySystem _inventorySystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DamageOnInteractComponent, InteractHandEvent>(OnHandInteract);
+    }
+
+    /// <summary>
+    /// Damages the user that interacts with the entity with an empty hand and
+    /// plays a sound or pops up text in response. If the user does not have
+    /// proper protection, the user will only be damaged and other interactions
+    /// will be cancelled.
+    /// </summary>
+    /// <param name="entity">The entity being interacted with</param>
+    /// <param name="args">Contains the user that interacted with the entity</param>
+    private void OnHandInteract(Entity<DamageOnInteractComponent> entity, ref InteractHandEvent args)
+    {
+        if (!entity.Comp.IsDamageActive)
+            return;
+
+        var totalDamage = entity.Comp.Damage;
+
+        if (!entity.Comp.IgnoreResistances)
+        {
+            // try to get damage on interact protection from either the inventory slots of the entity
+            _inventorySystem.TryGetInventoryEntity<DamageOnInteractProtectionComponent>(args.User, out var protectiveEntity);
+
+            // or checking the entity for  the comp itself if the inventory didn't work
+            if (protectiveEntity.Comp == null && TryComp<DamageOnInteractProtectionComponent>(args.User, out var protectiveComp))
+            {
+                protectiveEntity = (args.User, protectiveComp);
+            }
+
+            // if protectiveComp isn't null after all that, it means the user has protection,
+            // so let's calculate how much they resist
+            if (protectiveEntity.Comp != null)
+            {
+                totalDamage = DamageSpecifier.ApplyModifierSet(totalDamage, protectiveEntity.Comp.DamageProtection);
+            }
+        }
+
+        totalDamage = _damageableSystem.TryChangeDamage(args.User, totalDamage,  origin: args.Target);
+
+        if (totalDamage != null && totalDamage.AnyPositive())
+        {
+            args.Handled = true;
+            _adminLogger.Add(LogType.Damaged, $"{ToPrettyString(args.User):user} injured their hand by interacting with {ToPrettyString(args.Target):target} and received {totalDamage.GetTotal():damage} damage");
+            _audioSystem.PlayPredicted(entity.Comp.InteractSound, args.Target, args.User);
+
+            if (entity.Comp.PopupText != null)
+                _popupSystem.PopupClient(Loc.GetString(entity.Comp.PopupText), args.User, args.User);
+        }
+    }
+
+    public void SetIsDamageActiveTo(Entity<DamageOnInteractComponent> entity, bool mode)
+    {
+        if (entity.Comp.IsDamageActive == mode)
+            return;
+
+        entity.Comp.IsDamageActive = mode;
+        Dirty(entity);
+    }
+}
index 228b788722eca6d6376d6d8289abd45242c716ee..2522dd5d0a3388c3b4c3fe38518097d5afa1c6f9 100644 (file)
@@ -30,7 +30,7 @@ public partial class InventorySystem : EntitySystem
     /// <summary>
     /// Tries to find an entity in the specified slot with the specified component.
     /// </summary>
-    public bool TryGetInventoryEntity<T>(Entity<InventoryComponent?> entity, out EntityUid targetUid)
+    public bool TryGetInventoryEntity<T>(Entity<InventoryComponent?> entity, out Entity<T?> target)
         where T : IComponent, IClothingSlots
     {
         if (TryGetContainerSlotEnumerator(entity.Owner, out var containerSlotEnumerator))
@@ -43,12 +43,12 @@ public partial class InventorySystem : EntitySystem
                 if ((((IClothingSlots) required).Slots & slot.SlotFlags) == 0x0)
                     continue;
 
-                targetUid = item;
+                target = (item, required);
                 return true;
             }
         }
 
-        targetUid = EntityUid.Invalid;
+        target = EntityUid.Invalid;
         return false;
     }
 
index 02cf0ab6f67d4d0141dab92187eebc9b71b6d5c0..c1a53ccf6e5f0dacd434eb897562d0a28d6cfdc2 100644 (file)
     tags:
     - ClothMade
     - WhitelistChameleon
+  - type: DamageOnInteractProtection
+    damageProtection:
+      flatReductions:
+        Heat: 5 # the average lightbulb only does around four damage!
 
 - type: entity
   abstract: true
index 0fa298b648bd11f65e1107fca27802a8f97c8b1b..556442cee118b60af171872646a1246b8825044e 100644 (file)
         color: "#535353"
   - type: Clothing
     sprite: Clothing/Hands/Gloves/Color/black.rsi
-  - type: GloveHeatResistance
-    heatResistance: 1400
   - type: Butcherable
     butcheringType: Knife
     spawned:
     sprite: Clothing/Hands/Gloves/Color/yellow.rsi
   - type: Clothing
     sprite: Clothing/Hands/Gloves/Color/yellow.rsi
-  - type: GloveHeatResistance
-    heatResistance: 1400
   - type: Butcherable
     butcheringType: Knife
     spawned:
   name: budget insulated gloves
   description: These gloves are cheap knockoffs of the coveted ones - no way this can end badly.
   components:
-  - type: GloveHeatResistance
-    # can't take out lights using budgets
-    heatResistance: 0
   - type: RandomInsulation
     # Why repeated numbers? So some numbers are more common, of course!
     list:
index f1d99884658a487fd2aaeab968769ae29b5bb1e3..53a165cd57165fdfcff4314ea47bf2c2938cc608 100644 (file)
     sprite: Clothing/Hands/Gloves/captain.rsi
   - type: Clothing
     sprite: Clothing/Hands/Gloves/captain.rsi
-  - type: GloveHeatResistance
-    heatResistance: 1400
   - type: Insulated
   - type: Fiber
     fiberMaterial: fibers-durathread
     sprite: Clothing/Hands/Gloves/leather.rsi
   - type: Clothing
     sprite: Clothing/Hands/Gloves/leather.rsi
-  - type: GloveHeatResistance
-    heatResistance: 1400
   - type: Fiber
     fiberMaterial: fibers-leather
     fiberColor: fibers-brown
         enum.ToggleVisuals.Layer:
           True: {state: icon-green}
           False: {state: icon}
-  - type: GloveHeatResistance
-    heatResistance: 1400
   - type: Insulated
   - type: Fiber
     fiberMaterial: fibers-nanomachines
     sprite: Clothing/Hands/Gloves/combat.rsi
   - type: Clothing
     sprite: Clothing/Hands/Gloves/combat.rsi
-  - type: GloveHeatResistance
-    heatResistance: 1400
   - type: Insulated
   - type: Fiber
     fiberMaterial: fibers-insulative
     sprite: Clothing/Hands/Gloves/tacticalmaidgloves.rsi
   - type: Clothing
     sprite: Clothing/Hands/Gloves/tacticalmaidgloves.rsi
-  - type: GloveHeatResistance
-    heatResistance: 1400
   - type: Insulated
   - type: Fiber
     fiberColor: fibers-black
   - type: Fiber
     fiberMaterial: fibers-synthetic
     fiberColor: fibers-black
+  - type: DamageOnInteractProtection
+    damageProtection:
+      flatReductions:
+        Heat: 0 # no protection
 
 - type: entity
   parent: ClothingHandsBase
   - type: Fiber
     fiberMaterial: fibers-insulative
     fiberColor: fibers-yellow
+  - type: DamageOnInteractProtection
+    damageProtection:
+      flatReductions:
+        Heat: 0 # no protection
 
 - type: entity
   parent: ClothingHandsButcherable
   - type: Fiber
     fiberMaterial: fibers-insulative
     fiberColor: fibers-olive
+  - type: DamageOnInteractProtection
+    damageProtection:
+      flatReductions:
+        Heat: 0 # no protection
 
 - type: entity
   # Intentionally named after regular gloves, they're meant to be sneaky.
index 7c264a596a0c97b8260fd6863aee600bee43f88f..5df6adffec96104bb357073a06f55ff01dd954c0 100644 (file)
     - WhitelistChameleon
   - type: ClothingRequiredStepTriggerImmune
     slots: WITHOUT_POCKET
+  - type: DamageOnInteractProtection
+    damageProtection:
+      flatReductions:
+        Heat: 10 # the average lightbulb only does around four damage!
+    slots: OUTERCLOTHING
 
 - type: entity
   abstract: true
     size: Huge
   - type: ClothingRequiredStepTriggerImmune
     slots: WITHOUT_POCKET
+  - type: DamageOnInteractProtection
+    damageProtection:
+      flatReductions:
+        Heat: 10 # the average lightbulb only does around four damage!
+    slots: OUTERCLOTHING
 
 - type: entity
   parent: ClothingOuterBase
index 0b1f200d4bf32a6c3cb27caae6d65ed8bccc135d..371c102eb0aeb2637f897ebaf54436c88f539de0 100644 (file)
     - Cyborgs
     - Robotics
   - type: StepTriggerImmune
+  - type: DamageOnInteractProtection
+    damageProtection:
+      flatReductions:
+        Heat: 10 # capable of touching light bulbs and stoves without feeling pain!
 
 - type: entity
   abstract: true
index 6f9935d351d39eb24dfec20892f775fb69df509f..1db787a66d8407d93637f230581381bbaac5acab 100644 (file)
         collection: BananiumHorn
         params:
           volume: 5
-      anomalyContactDamage:
-        types:
-          Radiation: 10
     - type: Input
       context: "human"
     - type: Bloodstream
   suffix: "Ice"
   components:
   - type: Anomaly
-    anomalyContactDamage:
-      types:
-        Cold: 10
   - type: ExplosionAnomaly
     supercriticalExplosion: Cryo
     explosionTotalIntensity: 1000
index 99f874406b421a94c643cabe860278a2916736f6..929f509710ec2551e1993899b8008b6c89847762 100644 (file)
   - type: ContainerContainer
     containers:
       core_slot: !type:ContainerSlot
+  - type: DamageOnAttackedProtection
+    damageProtection:
+      flatReductions:
+        Heat: 100
+        Cold: 100
+        Radiation: 100
+    slots: NONE
index 0bab2c828dec974e5e601a0731817b28fd1f514c..838cde619faf1b3d1c2d2a3ff534199ecf15fade 100644 (file)
   - type: DisarmMalus
 
 - type: entity
-  name: The Throngler
+  name: Throngler
   parent: BaseSword
   id: Throngler
   description: Why would you make this?
       size: Ginormous
       sprite: Objects/Weapons/Melee/Throngler-in-hand.rsi
     - type: DisarmMalus
+    - type: Grammar
+      attributes:
+        proper: true
index 436eaa2697f07eaa0ce8ab81b2d1931a5af9b476..f684ca3425a3cd3a8945ec02dee464cbc1b1c134 100644 (file)
@@ -89,9 +89,6 @@
     enabled: false
   - type: PoweredLight
     bulb: Tube
-    damage:
-      types:
-        Heat: 2
   - type: ContainerContainer
     containers:
       light_bulb: !type:ContainerSlot
       on: base
       broken: broken
       burned: burned
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: Poweredlight
     enabled: true
   - type: PoweredLight
     hasLampOnSpawn: LightTube
-    damage:
-      types:
-        Heat: 2
   - type: AmbientOnPowered
   - type: AmbientSound
     volume: -15
     range: 2
     sound:
       path: /Audio/Ambience/Objects/light_hum.ogg
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 #LED lights
 - type: entity
   components:
   - type: PoweredLight
     hasLampOnSpawn: LedLightTube
-    damage:
-      types:
-        Heat: 1 #LEDs don't get as hot
   - type: PointLight
     radius: 15
     energy: 1
     softness: 0.9
     color: "#EEEEFF"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 1 # LEDs don't get as hot
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   parent: AlwaysPoweredWallLight
   components:
   - type: PoweredLight
     hasLampOnSpawn: ExteriorLightTube
+  - type: DamageOnInteract
     damage:
       types:
-        Heat: 4 #brighter light gets hotter
+        Heat: 4 # brighter light gets hotter
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   parent: AlwaysPoweredWallLight
   components:
   - type: PoweredLight
     hasLampOnSpawn: SodiumLightTube
-    damage:
-      types:
-        Heat: 2
   - type: PointLight
     radius: 10
     energy: 2.5
     softness: 0.9
     color: "#FFAF38"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   parent: AlwaysPoweredWallLight
       light_bulb: !type:ContainerSlot
   - type: PoweredLight
     bulb: Bulb
-    damage:
-      types:
-        Heat: 2
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: DeviceNetwork
       - On
       - Off
       - Toggle
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: PoweredLEDSmallLight
     color: "#EEEEFF"
   - type: PoweredLight
     hasLampOnSpawn: LedLightBulb
+  - type: DamageOnInteract
     damage:
       types:
         Heat: 1
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: PoweredSmallLight
     enabled: true
   - type: PoweredLight
     hasLampOnSpawn: LightBulb
+  - type: DamageOnInteract
     damage:
       types:
         Heat: 2
+    popupText: powered-light-component-burn-hand
 
 #Emergency Lights
 - type: entity
   components:
   - type: PoweredLight
     hasLampOnSpawn: LightTubeCrystalCyan
-    damage:
-      types:
-        Heat: 2
   - type: PointLight
     radius: 8
     energy: 3
     softness: 0.5
     color: "#47f8ff"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: AlwaysPoweredlightCyan
   components:
   - type: PoweredLight
     hasLampOnSpawn: LightTubeCrystalBlue
-    damage:
-      types:
-        Heat: 2
   - type: PointLight
     radius: 8
     energy: 3
     softness: 0.5
     color: "#39a1ff"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: AlwaysPoweredlightBlue
   components:
   - type: PoweredLight
     hasLampOnSpawn: LightTubeCrystalPink
-    damage:
-      types:
-        Heat: 2
   - type: PointLight
     radius: 8
     energy: 3
     softness: 0.5
     color: "#ff66cc"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: AlwaysPoweredlightPink
   components:
   - type: PoweredLight
     hasLampOnSpawn: LightTubeCrystalOrange
-    damage:
-      types:
-        Heat: 2
   - type: PointLight
     radius: 8
     energy: 3
     softness: 0.5
     color: "#ff8227"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: AlwaysPoweredlightOrange
   components:
   - type: PoweredLight
     hasLampOnSpawn: LightTubeCrystalRed
-    damage:
-      types:
-        Heat: 2
   - type: PointLight
     radius: 8
     energy: 3
     softness: 0.5
     color: "#fb4747"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: AlwaysPoweredlightRed
   components:
   - type: PoweredLight
     hasLampOnSpawn: LightTubeCrystalGreen
-    damage:
-      types:
-        Heat: 2
   - type: PointLight
     radius: 8
     energy: 3
     softness: 0.5
     color: "#52ff39"
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: AlwaysPoweredlightGreen
index 2afde4ef3fce79117b6adadedfc42152f821d0e2..0771cb71bc067fa6d4960a072ba295bcc31a2cb9 100644 (file)
@@ -77,9 +77,6 @@
     enabled: false
   - type: PoweredLight
     bulb: Tube
-    damage:
-      types:
-        Heat: 2
   - type: ContainerContainer
     containers:
       light_bulb: !type:ContainerSlot
       on: base
       broken: broken
       burned: burned
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: PoweredLightPostSmall
     enabled: true
   - type: PoweredLight
     hasLampOnSpawn: LightTube
-    damage:
-      types:
-        Heat: 2
   - type: StaticPrice
     price: 25
   - type: AmbientOnPowered
     range: 3
     sound:
       path: /Audio/Ambience/Objects/light_hum.ogg
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: PoweredLEDLightPostSmall
     color: "#EEEEFF"
   - type: PoweredLight
     hasLampOnSpawn: LedLightTube
-    damage:
-      types:
-        Heat: 1
   - type: StaticPrice
     price: 25
   - type: AmbientOnPowered
     range: 3
     sound:
       path: /Audio/Ambience/Objects/light_hum.ogg
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 1
+    popupText: powered-light-component-burn-hand
index 72f5439646991759f4c3adbf38f9d7e98dbabd0d..50e24eab73d20099d0590d9ffc557eeeef0306c6 100644 (file)
@@ -87,9 +87,6 @@
   - type: PoweredLight
     bulb: Bulb
     on: false
-    damage:
-      types:
-        Heat: 2
   - type: ApcPowerReceiver
   - type: ExtensionCableReceiver
   - type: DeviceNetwork
   - type: Construction
     graph: LightFixture
     node: strobeLight
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 2
+    popupText: powered-light-component-burn-hand
 
 - type: entity
   id: PoweredStrobeLightPolice
index 8bca49c4e5fedf0703bb2aef6594822c61ffb75b..a16aaaabbbe3770b49c4373595216102842832fb 100644 (file)
@@ -9,9 +9,6 @@
       collection: RadiationPulse
       params:
         volume: 5
-    anomalyContactDamage:
-      types:
-        Radiation: 10
   - type: AmbientSound
     range: 5
     volume: -5
   - type: SecretDataAnomaly
     randomStartSecretMin: 0
     randomStartSecretMax: 2
+  - type: DamageOnInteract
+    damage:
+      types:
+        Radiation: 10
+    popupText: anomaly-component-contact-damage
+  - type: DamageOnAttacked
+    damage:
+      types:
+        Radiation: 10
 
 - type: entity
   id: AnomalyPyroclastic
   - type: IgniteOnCollide
     fixtureId: fix1
     fireStacks: 1
+  - type: DamageOnInteract
+    damage:
+      types:
+        Heat: 10
+    popupText: anomaly-component-contact-damage
+  - type: DamageOnAttacked
+    damage:
+      types:
+        Heat: 10
 
 - type: entity
   id: AnomalyGravity
       collection: RadiationPulse
       params:
         volume: 5
-    anomalyContactDamage:
-      types:
-        Radiation: 10
 
 - type: entity
   id: AnomalyIce
   - type: Anomaly
     corePrototype: AnomalyCoreIce
     coreInertPrototype: AnomalyCoreIceInert
-    anomalyContactDamage:
-      types:
-        Cold: 10
   - type: ExplosionAnomaly
     supercriticalExplosion: Cryo
     explosionTotalIntensity: 300
     releasedGas: 8 # Frezon. Please replace if there is a better way to specify this
     releaseOnMaxSeverity: true
     spawnRadius: 0
+  - type: DamageOnInteract
+    damage:
+      types:
+        Cold: 10
+    popupText: anomaly-component-contact-damage
+  - type: DamageOnAttacked
+    damage:
+      types:
+        Cold: 10
 
 - type: entity
   id: AnomalyRockBase
     coreInertPrototype: AnomalyCoreFloraInert
     minPulseLength: 60
     maxPulseLength: 120
-    anomalyContactDamage:
-      types:
-        Slash: 0
   - type: TileSpawnAnomaly
     entries:
     - settings:
     coreInertPrototype: AnomalyCoreLiquidInert
     minPulseLength: 60
     maxPulseLength: 120
-    anomalyContactDamage:
-      types:
-        Slash: 1
   - type: EntitySpawnAnomaly
     entries:
     - settings:
     coreInertPrototype: AnomalyCoreShadowInert
     minPulseLength: 60
     maxPulseLength: 120
-    anomalyContactDamage:
-      types:
-        Cold: 10
     animationTime: 4
     offset: "-0.1,0.1"
   - type: EntitySpawnAnomaly
   - type: Tag
     tags:
       - SpookyFog
+  - type: DamageOnInteract
+    damage:
+      types:
+        Cold: 10
+    popupText: anomaly-component-contact-damage
+  - type: DamageOnAttacked
+    damage:
+      types:
+        Cold: 10
index b8aaa60c37238971a5e67dac192a4f4d7a9ff63c..baadcb67dbf8a4f29869cc4baf1a8cc67fba7d30 100644 (file)
@@ -24,6 +24,8 @@
     bulb: Bulb
     on: false
     hasLampOnSpawn: ServiceLightBulb
+  - type: DamageOnInteract
     damage:
       types:
         Heat: 5
+    popupText: powered-light-component-burn-hand