Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Generic DamageOnInteract/Attacked Comps/Systems #30244

Merged
merged 15 commits into from
Aug 9, 2024
12 changes: 0 additions & 12 deletions Content.Server/Clothing/Components/GloveHeatResistanceComponent.cs

This file was deleted.

4 changes: 0 additions & 4 deletions Content.Server/Light/Components/PoweredLightComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ public sealed partial class PoweredLightComponent : Component
[DataField("on")]
public bool On = true;

[DataField("damage", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier Damage = default!;

[DataField("ignoreGhostsBoo")]
public bool IgnoreGhostsBoo;

Expand Down
34 changes: 0 additions & 34 deletions Content.Server/Light/EntitySystems/PoweredLightSystem.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -106,40 +105,7 @@ private void OnInteractHand(EntityUid uid, PoweredLightComponent light, Interact
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)
{
Expand Down
14 changes: 0 additions & 14 deletions Content.Shared/Anomaly/Components/AnomalyComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
26 changes: 0 additions & 26 deletions Content.Shared/Anomaly/SharedAnomalySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
Expand All @@ -42,26 +41,10 @@ public override void Initialize()
{
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))
Expand All @@ -75,15 +58,6 @@ private void OnAnomalyThrowEnd(Entity<AnomalyComponent> ent, ref MeleeThrowOnHit
_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))
Expand Down
40 changes: 40 additions & 0 deletions Content.Shared/Damage/Components/DamageOnAttackedComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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]
public sealed partial class DamageOnAttackedComponent : Component
{
/// <summary>
/// How much damage to apply to the person making contact
/// </summary>
[DataField]
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");
}
Original file line number Diff line number Diff line change
@@ -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]
public DamageSpecifier 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.WITHOUT_POCKET;
}
40 changes: 40 additions & 0 deletions Content.Shared/Damage/Components/DamageOnInteractComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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]
public sealed partial class DamageOnInteractComponent : Component
{
/// <summary>
/// How much damage to apply to the person making contact
/// </summary>
[DataField]
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 = 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");
}
Original file line number Diff line number Diff line change
@@ -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]
public DamageSpecifier 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;
}
78 changes: 78 additions & 0 deletions Content.Shared/Damage/Systems/DamageOnAttackedSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Components;
using Content.Shared.Database;
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)
{
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);

// or checking the entity for the comp itself if the inventory didn't work
if (protectiveEntity.Comp == null && TryComp<DamageOnAttackedProtectionComponent>(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 -= protectiveEntity.Comp.DamageProtection;
totalDamage.ClampMin(0); // don't let them heal just because they have enough protection
}
}

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");

if (!_gameTiming.IsFirstTimePredicted || _net.IsServer)
return;

_audioSystem.PlayPvs(entity.Comp.InteractSound, entity);

if (entity.Comp.PopupText != null)
_popupSystem.PopupClient(Loc.GetString(entity.Comp.PopupText), args.User, args.User);

}
}
}
Loading
Loading