diff --git a/Content.Client/Damage/Systems/DamageOtherOnHitSystem.cs b/Content.Client/Damage/Systems/DamageOtherOnHitSystem.cs new file mode 100644 index 0000000000..a78603c22c --- /dev/null +++ b/Content.Client/Damage/Systems/DamageOtherOnHitSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Damage.Systems; + +namespace Content.Client.Damage.Systems; + +public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem; diff --git a/Content.Server/Damage/Components/DamageOtherOnHitComponent.cs b/Content.Server/Damage/Components/DamageOtherOnHitComponent.cs deleted file mode 100644 index 3123e251af..0000000000 --- a/Content.Server/Damage/Components/DamageOtherOnHitComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Server.Damage.Systems; -using Content.Shared.Damage; - -namespace Content.Server.Damage.Components -{ - [Access(typeof(DamageOtherOnHitSystem))] - [RegisterComponent] - public sealed partial class DamageOtherOnHitComponent : Component - { - [DataField("ignoreResistances")] - [ViewVariables(VVAccess.ReadWrite)] - public bool IgnoreResistances = false; - - [DataField("damage", required: true)] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier Damage = default!; - - } -} diff --git a/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs b/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs index b1f5365b01..10930ca5c9 100644 --- a/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs +++ b/Content.Server/Damage/Systems/DamageOtherOnHitSystem.cs @@ -1,72 +1,54 @@ using Content.Server.Administration.Logs; -using Content.Server.Damage.Components; using Content.Server.Weapons.Ranged.Systems; -using Content.Shared.CombatMode.Pacification; using Content.Shared.Camera; using Content.Shared.Damage; -using Content.Shared.Damage.Events; +using Content.Shared.Damage.Components; using Content.Shared.Damage.Systems; using Content.Shared.Database; using Content.Shared.Effects; using Content.Shared.Mobs.Components; using Content.Shared.Throwing; -using Content.Shared.Wires; using Robust.Shared.Physics.Components; using Robust.Shared.Player; -namespace Content.Server.Damage.Systems +namespace Content.Server.Damage.Systems; + +public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem { - public sealed class DamageOtherOnHitSystem : EntitySystem + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly GunSystem _guns = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!; + [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; + + public override void Initialize() { - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly GunSystem _guns = default!; - [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly DamageExamineSystem _damageExamine = default!; - [Dependency] private readonly SharedCameraRecoilSystem _sharedCameraRecoil = default!; - [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnDoHit); + } + + private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args) + { + if (TerminatingOrDeleted(args.Target)) + return; + + var dmg = _damageable.TryChangeDamage(args.Target, component.Damage * _damageable.UniversalThrownDamageModifier, component.IgnoreResistances, origin: args.Component.Thrower); + + // Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying. + if (dmg != null && HasComp(args.Target)) + _adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision"); + + if (dmg is { Empty: false }) { - SubscribeLocalEvent(OnDoHit); - SubscribeLocalEvent(OnDamageExamine); - SubscribeLocalEvent(OnAttemptPacifiedThrow); + _color.RaiseEffect(Color.Red, [args.Target], Filter.Pvs(args.Target, entityManager: EntityManager)); } - private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args) + _guns.PlayImpactSound(args.Target, dmg, null, false); + if (TryComp(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f) { - if (TerminatingOrDeleted(args.Target)) - return; - - var dmg = _damageable.TryChangeDamage(args.Target, component.Damage * _damageable.UniversalThrownDamageModifier, component.IgnoreResistances, origin: args.Component.Thrower); - - // Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying. - if (dmg != null && HasComp(args.Target)) - _adminLogger.Add(LogType.ThrowHit, $"{ToPrettyString(args.Target):target} received {dmg.GetTotal():damage} damage from collision"); - - if (dmg is { Empty: false }) - { - _color.RaiseEffect(Color.Red, new List() { args.Target }, Filter.Pvs(args.Target, entityManager: EntityManager)); - } - - _guns.PlayImpactSound(args.Target, dmg, null, false); - if (TryComp(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f) - { - var direction = body.LinearVelocity.Normalized(); - _sharedCameraRecoil.KickCamera(args.Target, direction); - } - } - - private void OnDamageExamine(EntityUid uid, DamageOtherOnHitComponent component, ref DamageExamineEvent args) - { - _damageExamine.AddDamageExamine(args.Message, _damageable.ApplyUniversalAllModifiers(component.Damage * _damageable.UniversalThrownDamageModifier), Loc.GetString("damage-throw")); - } - - /// - /// Prevent players with the Pacified status effect from throwing things that deal damage. - /// - private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) - { - args.Cancel("pacified-cannot-throw"); + var direction = body.LinearVelocity.Normalized(); + _sharedCameraRecoil.KickCamera(args.Target, direction); } } } diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 1e8f3334ac..31d289217c 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -1,7 +1,5 @@ using Content.Server.Chat.Systems; using Content.Server.Movement.Systems; -using Content.Shared.Damage.Events; -using Content.Shared.Damage.Systems; using Content.Shared.Effects; using Content.Shared.Speech.Components; using Content.Shared.Weapons.Melee; @@ -16,28 +14,14 @@ namespace Content.Server.Weapons.Melee; public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem { [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly DamageExamineSystem _damageExamine = default!; [Dependency] private readonly LagCompensationSystem _lag = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnSpeechHit); - SubscribeLocalEvent(OnMeleeExamineDamage); - } - - private void OnMeleeExamineDamage(EntityUid uid, MeleeWeaponComponent component, ref DamageExamineEvent args) - { - if (component.Hidden) - return; - - var damageSpec = GetDamage(uid, args.User, component); - - if (damageSpec.Empty) - return; - - _damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), Loc.GetString("damage-melee")); } protected override bool ArcRaySuccessful(EntityUid targetUid, diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs index d697e2bef1..700a15e1b0 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.Battery.cs @@ -1,12 +1,7 @@ -using Content.Shared.Damage; -using Content.Shared.Damage.Events; using Content.Shared.Power; using Content.Shared.PowerCell.Components; -using Content.Shared.Projectiles; -using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; -using Robust.Shared.Prototypes; namespace Content.Server.Weapons.Ranged.Systems; @@ -19,13 +14,11 @@ public sealed partial class GunSystem // Hitscan SubscribeLocalEvent(OnBatteryStartup); SubscribeLocalEvent(OnBatteryChargeChange); - SubscribeLocalEvent(OnBatteryDamageExamine); SubscribeLocalEvent(OnPowerCellChanged); // Projectile SubscribeLocalEvent(OnBatteryStartup); SubscribeLocalEvent(OnBatteryChargeChange); - SubscribeLocalEvent(OnBatteryDamageExamine); SubscribeLocalEvent(OnPowerCellChanged); } @@ -73,50 +66,6 @@ public sealed partial class GunSystem RaiseLocalEvent(uid, ref updateAmmoEv); } - private void OnBatteryDamageExamine(Entity entity, ref DamageExamineEvent args) where T : BatteryAmmoProviderComponent - { - var damageSpec = GetDamage(entity.Comp); - - if (damageSpec == null) - return; - - var damageType = entity.Comp switch - { - HitscanBatteryAmmoProviderComponent => Loc.GetString("damage-hitscan"), - ProjectileBatteryAmmoProviderComponent => Loc.GetString("damage-projectile"), - _ => throw new ArgumentOutOfRangeException(), - }; - - _damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), damageType); - } - - private DamageSpecifier? GetDamage(BatteryAmmoProviderComponent component) - { - if (component is ProjectileBatteryAmmoProviderComponent battery) - { - if (ProtoManager.Index(battery.Prototype).Components - .TryGetValue(Factory.GetComponentName(), out var projectile)) - { - var p = (ProjectileComponent) projectile.Component; - - if (!p.Damage.Empty) - { - return p.Damage * Damageable.UniversalProjectileDamageModifier; - } - } - - return null; - } - - if (component is HitscanBatteryAmmoProviderComponent hitscan) - { - var dmg = ProtoManager.Index(hitscan.Prototype).Damage; - return dmg == null ? dmg : dmg * Damageable.UniversalHitscanDamageModifier; - } - - return null; - } - protected override void TakeCharge(Entity entity) { var ev = new ChangeChargeEvent(-entity.Comp.FireCost); diff --git a/Content.Shared/Damage/Components/DamageExaminableComponent.cs b/Content.Shared/Damage/Components/DamageExaminableComponent.cs index a9fa833070..0ac9435bb6 100644 --- a/Content.Shared/Damage/Components/DamageExaminableComponent.cs +++ b/Content.Shared/Damage/Components/DamageExaminableComponent.cs @@ -2,7 +2,8 @@ using Robust.Shared.GameStates; namespace Content.Shared.Damage.Components; +/// +/// Shows a detailed examine window with this entity's damage stats when examined. +/// [RegisterComponent, NetworkedComponent] -public sealed partial class DamageExaminableComponent : Component -{ -} +public sealed partial class DamageExaminableComponent : Component; diff --git a/Content.Shared/Damage/Components/DamageOtherOnHitComponent.cs b/Content.Shared/Damage/Components/DamageOtherOnHitComponent.cs new file mode 100644 index 0000000000..7728bcf7ee --- /dev/null +++ b/Content.Shared/Damage/Components/DamageOtherOnHitComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.Damage.Systems; + +namespace Content.Shared.Damage.Components; + +/// +/// Makes this entity deal damage when thrown at something. +/// +[RegisterComponent] +[Access(typeof(SharedDamageOtherOnHitSystem))] +public sealed partial class DamageOtherOnHitComponent : Component +{ + /// + /// Whether to ignore damage modifiers. + /// + [DataField] + public bool IgnoreResistances = false; + + /// + /// The damage amount to deal on hit. + /// + [DataField(required: true)] + public DamageSpecifier Damage = default!; + +} diff --git a/Content.Shared/Damage/Events/DamageExamineEvent.cs b/Content.Shared/Damage/Events/DamageExamineEvent.cs index d3e6175f49..33949cc052 100644 --- a/Content.Shared/Damage/Events/DamageExamineEvent.cs +++ b/Content.Shared/Damage/Events/DamageExamineEvent.cs @@ -1,6 +1,10 @@ +using Content.Shared.Damage.Components; using Robust.Shared.Utility; namespace Content.Shared.Damage.Events; +/// +/// Raised on an entity with when examined to get the damage values displayed in the examine window. +/// [ByRefEvent] public readonly record struct DamageExamineEvent(FormattedMessage Message, EntityUid User); diff --git a/Content.Shared/Damage/Systems/SharedDamageOtherOnHitSystem.cs b/Content.Shared/Damage/Systems/SharedDamageOtherOnHitSystem.cs new file mode 100644 index 0000000000..f17a91972d --- /dev/null +++ b/Content.Shared/Damage/Systems/SharedDamageOtherOnHitSystem.cs @@ -0,0 +1,32 @@ +using Content.Shared.CombatMode.Pacification; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Events; + +namespace Content.Shared.Damage.Systems; + +public abstract class SharedDamageOtherOnHitSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly DamageExamineSystem _damageExamine = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDamageExamine); + SubscribeLocalEvent(OnAttemptPacifiedThrow); + } + + private void OnDamageExamine(Entity ent, ref DamageExamineEvent args) + { + _damageExamine.AddDamageExamine(args.Message, _damageable.ApplyUniversalAllModifiers(ent.Comp.Damage * _damageable.UniversalThrownDamageModifier), Loc.GetString("damage-throw")); + } + + /// + /// Prevent players with the Pacified status effect from throwing things that deal damage. + /// + private void OnAttemptPacifiedThrow(Entity ent, ref AttemptPacifiedThrowEvent args) + { + args.Cancel("pacified-cannot-throw"); + } +} diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 7d0d7577a4..9bc0b845ae 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Administration.Components; using Content.Shared.Administration.Logs; using Content.Shared.CombatMode; using Content.Shared.Damage; +using Content.Shared.Damage.Events; using Content.Shared.Damage.Systems; using Content.Shared.Database; using Content.Shared.FixedPoint; @@ -63,6 +64,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem [Dependency] protected readonly SharedPopupSystem PopupSystem = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedStaminaSystem _stamina = default!; + [Dependency] private readonly DamageExamineSystem _damageExamine = default!; private const int AttackMask = (int) (CollisionGroup.MobMask | CollisionGroup.Opaque); @@ -83,6 +85,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem SubscribeLocalEvent(OnMeleeSelected); SubscribeLocalEvent(OnMeleeShotAttempted); SubscribeLocalEvent(OnMeleeShot); + SubscribeLocalEvent(OnMeleeExamineDamage); SubscribeLocalEvent(OnGetBonusMeleeDamage); SubscribeLocalEvent(OnGetBonusHeavyDamageModifier); SubscribeLocalEvent(OnGetBonusMeleeAttackRate); @@ -95,8 +98,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem SubscribeAllEvent(OnStopAttack); #if DEBUG - SubscribeLocalEvent (OnMapInit); + SubscribeLocalEvent(OnMapInit); } private void OnMapInit(EntityUid uid, MeleeWeaponComponent component, MapInitEvent args) @@ -124,6 +126,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem } } + private void OnMeleeExamineDamage(EntityUid uid, MeleeWeaponComponent component, ref DamageExamineEvent args) + { + if (component.Hidden) + return; + + var damageSpec = GetDamage(uid, args.User, component); + + if (damageSpec.Empty) + return; + + _damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), Loc.GetString("damage-melee")); + } private void OnMeleeSelected(EntityUid uid, MeleeWeaponComponent component, HandSelectedEvent args) { var attackRate = GetAttackRate(uid, args.User, component); diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs index ea865fced6..cb4e42f3cf 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs @@ -1,8 +1,12 @@ +using Content.Shared.Damage; +using Content.Shared.Damage.Events; using Content.Shared.Examine; +using Content.Shared.Projectiles; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Robust.Shared.GameStates; using Robust.Shared.Map; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Weapons.Ranged.Systems; @@ -18,6 +22,7 @@ public abstract partial class SharedGunSystem SubscribeLocalEvent(OnBatteryTakeAmmo); SubscribeLocalEvent(OnBatteryAmmoCount); SubscribeLocalEvent(OnBatteryExamine); + SubscribeLocalEvent(OnBatteryDamageExamine); // Projectile SubscribeLocalEvent(OnBatteryGetState); @@ -25,6 +30,7 @@ public abstract partial class SharedGunSystem SubscribeLocalEvent(OnBatteryTakeAmmo); SubscribeLocalEvent(OnBatteryAmmoCount); SubscribeLocalEvent(OnBatteryExamine); + SubscribeLocalEvent(OnBatteryDamageExamine); } private void OnBatteryHandleState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentHandleState args) @@ -53,6 +59,51 @@ public abstract partial class SharedGunSystem args.PushMarkup(Loc.GetString("gun-battery-examine", ("color", AmmoExamineColor), ("count", component.Shots))); } + private void OnBatteryDamageExamine(Entity entity, ref DamageExamineEvent args) where T : BatteryAmmoProviderComponent + { + var damageSpec = GetDamage(entity.Comp); + + if (damageSpec == null) + return; + + var damageType = entity.Comp switch + { + HitscanBatteryAmmoProviderComponent => Loc.GetString("damage-hitscan"), + ProjectileBatteryAmmoProviderComponent => Loc.GetString("damage-projectile"), + _ => throw new ArgumentOutOfRangeException(), + }; + + _damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), damageType); + } + + private DamageSpecifier? GetDamage(BatteryAmmoProviderComponent component) + { + if (component is ProjectileBatteryAmmoProviderComponent battery) + { + if (ProtoManager.Index(battery.Prototype) + .Components + .TryGetValue(Factory.GetComponentName(), out var projectile)) + { + var p = (ProjectileComponent)projectile.Component; + + if (!p.Damage.Empty) + { + return p.Damage * Damageable.UniversalProjectileDamageModifier; + } + } + + return null; + } + + if (component is HitscanBatteryAmmoProviderComponent hitscan) + { + var dmg = ProtoManager.Index(hitscan.Prototype).Damage; + return dmg == null ? dmg : dmg * Damageable.UniversalHitscanDamageModifier; + } + + return null; + } + private void OnBatteryTakeAmmo(EntityUid uid, BatteryAmmoProviderComponent component, TakeAmmoEvent args) { var shots = Math.Min(args.Shots, component.Shots);