diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index 8ea211ac9c..f69d62580e 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -1,5 +1,6 @@ using System; using Content.Client.Weapons.Melee.Components; +using Content.Shared.Examine; using Content.Shared.Weapons.Melee; using JetBrains.Annotations; using Robust.Client.GameObjects; diff --git a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs index 1e118378a3..017d1f008a 100644 --- a/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapon/Melee/MeleeWeaponSystem.cs @@ -7,13 +7,16 @@ using Content.Server.Chemistry.EntitySystems; using Content.Server.Cooldown; using Content.Server.Damage.Components; using Content.Server.Damage.Systems; +using Content.Server.Examine; using Content.Server.Weapon.Melee.Components; using Content.Shared.Damage; using Content.Shared.Audio; using Content.Shared.Database; +using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.Hands; using Content.Shared.Physics; +using Content.Shared.Verbs; using Content.Shared.Weapons.Melee; using Robust.Shared.Audio; using Robust.Shared.Containers; @@ -27,12 +30,14 @@ namespace Content.Server.Weapon.Melee { public sealed class MeleeWeaponSystem : EntitySystem { + [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly ExamineSystem _examine = default!; [Dependency] private readonly StaminaSystem _staminaSystem = default!; [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; public const float DamagePitchVariation = 0.15f; @@ -44,9 +49,41 @@ namespace Content.Server.Weapon.Melee SubscribeLocalEvent(OnHandSelected); SubscribeLocalEvent(OnClickAttack); SubscribeLocalEvent(OnWideAttack); + SubscribeLocalEvent>(OnMeleeExaminableVerb); SubscribeLocalEvent(OnChemicalInjectorHit); } + private void OnMeleeExaminableVerb(EntityUid uid, MeleeWeaponComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var damageSpec = GetDamage(component); + + if (damageSpec == null) + return; + + var verb = new ExamineVerb() + { + Act = () => + { + var markup = _damageable.GetDamageExamine(damageSpec, Loc.GetString("damage-melee")); + _examine.SendExamineTooltip(args.User, uid, markup, false, false); + }, + Text = Loc.GetString("damage-examinable-verb-text"), + Message = Loc.GetString("damage-examinable-verb-message"), + Category = VerbCategory.Examine, + IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png" + }; + + args.Verbs.Add(verb); + } + + private DamageSpecifier? GetDamage(MeleeWeaponComponent component) + { + return component.Damage.Total > FixedPoint2.Zero ? component.Damage : null; + } + private void OnHandSelected(EntityUid uid, MeleeWeaponComponent comp, HandSelectedEvent args) { var curTime = _gameTiming.CurTime; @@ -100,7 +137,7 @@ namespace Content.Server.Weapon.Melee RaiseLocalEvent(target, new AttackedEvent(args.Used, args.User, args.ClickLocation), true); var modifiedDamage = DamageSpecifier.ApplyModifierSets(comp.Damage + hitEvent.BonusDamage, hitEvent.ModifiersList); - var damageResult = _damageableSystem.TryChangeDamage(target, modifiedDamage); + var damageResult = _damageable.TryChangeDamage(target, modifiedDamage); if (damageResult != null && damageResult.Total > FixedPoint2.Zero) { @@ -196,7 +233,7 @@ namespace Content.Server.Weapon.Melee { RaiseLocalEvent(entity, new AttackedEvent(args.Used, args.User, args.ClickLocation), true); - var damageResult = _damageableSystem.TryChangeDamage(entity, modifiedDamage); + var damageResult = _damageable.TryChangeDamage(entity, modifiedDamage); if (damageResult != null && damageResult.Total > FixedPoint2.Zero) { diff --git a/Content.Server/Weapon/Ranged/Systems/GunSystem.Battery.cs b/Content.Server/Weapon/Ranged/Systems/GunSystem.Battery.cs index 34d089e62a..7767cf40f0 100644 --- a/Content.Server/Weapon/Ranged/Systems/GunSystem.Battery.cs +++ b/Content.Server/Weapon/Ranged/Systems/GunSystem.Battery.cs @@ -1,5 +1,11 @@ using Content.Server.Power.Components; +using Content.Server.Projectiles.Components; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Verbs; +using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Weapon.Ranged.Systems; @@ -12,10 +18,12 @@ public sealed partial class GunSystem // Hitscan SubscribeLocalEvent(OnBatteryStartup); SubscribeLocalEvent(OnBatteryChargeChange); + SubscribeLocalEvent>(OnBatteryExaminableVerb); // Projectile SubscribeLocalEvent(OnBatteryStartup); SubscribeLocalEvent(OnBatteryChargeChange); + SubscribeLocalEvent>(OnBatteryExaminableVerb); } private void OnBatteryStartup(EntityUid uid, BatteryAmmoProviderComponent component, ComponentStartup args) @@ -49,6 +57,72 @@ public sealed partial class GunSystem UpdateBatteryAppearance(component.Owner, component); } + private void OnBatteryExaminableVerb(EntityUid uid, BatteryAmmoProviderComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var damageSpec = GetDamage(component); + + if (damageSpec == null) + return; + + string damageType; + + switch (component) + { + case HitscanBatteryAmmoProviderComponent: + damageType = Loc.GetString("damage-hitscan"); + break; + case ProjectileBatteryAmmoProviderComponent: + damageType = Loc.GetString("damage-projectile"); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + var verb = new ExamineVerb() + { + Act = () => + { + var markup = Damageable.GetDamageExamine(damageSpec, damageType); + Examine.SendExamineTooltip(args.User, uid, markup, false, false); + }, + Text = Loc.GetString("damage-examinable-verb-text"), + Message = Loc.GetString("damage-examinable-verb-message"), + Category = VerbCategory.Examine, + IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png" + }; + + args.Verbs.Add(verb); + } + + private DamageSpecifier? GetDamage(BatteryAmmoProviderComponent component) + { + if (component is ProjectileBatteryAmmoProviderComponent battery) + { + if (ProtoManager.Index(battery.Prototype).Components + .TryGetValue(_factory.GetComponentName(typeof(ProjectileComponent)), out var projectile)) + { + var p = (ProjectileComponent) projectile.Component; + + if (p.Damage.Total > FixedPoint2.Zero) + { + return p.Damage; + } + } + + return null; + } + + if (component is HitscanBatteryAmmoProviderComponent hitscan) + { + return ProtoManager.Index(hitscan.Prototype).Damage; + } + + return null; + } + protected override void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) { if (!TryComp(uid, out var battery)) return; diff --git a/Content.Server/Weapon/Ranged/Systems/GunSystem.Cartridges.cs b/Content.Server/Weapon/Ranged/Systems/GunSystem.Cartridges.cs new file mode 100644 index 0000000000..0fc8b5b4d2 --- /dev/null +++ b/Content.Server/Weapon/Ranged/Systems/GunSystem.Cartridges.cs @@ -0,0 +1,76 @@ +using Content.Server.Projectiles.Components; +using Content.Shared.Damage; +using Content.Shared.Examine; +using Content.Shared.FixedPoint; +using Content.Shared.Verbs; +using Content.Shared.Weapons.Ranged.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server.Weapon.Ranged.Systems; + +public sealed partial class GunSystem +{ + protected override void InitializeCartridge() + { + base.InitializeCartridge(); + SubscribeLocalEvent(OnCartridgeExamine); + SubscribeLocalEvent>(OnCartridgeVerbExamine); + } + + private void OnCartridgeVerbExamine(EntityUid uid, CartridgeAmmoComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var damageSpec = GetProjectileDamage(component.Prototype); + + if (damageSpec == null) + return; + + var verb = new ExamineVerb() + { + Act = () => + { + var markup = Damageable.GetDamageExamine(damageSpec, Loc.GetString("damage-projectile")); + _examine.SendExamineTooltip(args.User, uid, markup, false, false); + }, + Text = Loc.GetString("damage-examinable-verb-text"), + Message = Loc.GetString("damage-examinable-verb-message"), + Category = VerbCategory.Examine, + IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png" + }; + + args.Verbs.Add(verb); + } + + private DamageSpecifier? GetProjectileDamage(string proto) + { + if (!ProtoManager.TryIndex(proto, out var entityProto)) + return null; + + if (entityProto.Components + .TryGetValue(_factory.GetComponentName(typeof(ProjectileComponent)), out var projectile)) + { + var p = (ProjectileComponent) projectile.Component; + + if (p.Damage.Total > FixedPoint2.Zero) + { + return p.Damage; + } + } + + return null; + } + + private void OnCartridgeExamine(EntityUid uid, CartridgeAmmoComponent component, ExaminedEvent args) + { + if (component.Spent) + { + args.PushMarkup(Loc.GetString("gun-cartridge-spent")); + } + else + { + args.PushMarkup(Loc.GetString("gun-cartridge-unspent")); + } + } +} diff --git a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs index 2b2069fdb1..d2e96ef8c3 100644 --- a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Damage.Systems; +using Content.Server.Examine; using Content.Server.Projectiles.Components; using Content.Server.Weapon.Melee; using Content.Server.Weapon.Ranged.Components; @@ -23,6 +24,8 @@ namespace Content.Server.Weapon.Ranged.Systems; public sealed partial class GunSystem : SharedGunSystem { + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly ExamineSystem _examine = default!; [Dependency] private readonly StaminaSystem _stamina = default!; public const float DamagePitchVariation = MeleeWeaponSystem.DamagePitchVariation; diff --git a/Content.Shared/Damage/Systems/DamageableSystem.cs b/Content.Shared/Damage/Systems/DamageableSystem.cs index 5c7726ddc8..cea2f2f541 100644 --- a/Content.Shared/Damage/Systems/DamageableSystem.cs +++ b/Content.Shared/Damage/Systems/DamageableSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.MobState.Components; using Content.Shared.Radiation.Events; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Shared.Damage { @@ -22,6 +23,31 @@ namespace Content.Shared.Damage SubscribeLocalEvent(OnIrradiated); } + /// + /// Retrieves the damage examine values. + /// + public FormattedMessage GetDamageExamine(DamageSpecifier damageSpecifier, string? type = null) + { + var msg = new FormattedMessage(); + + if (string.IsNullOrEmpty(type)) + { + msg.AddMarkup(Loc.GetString("damage-examine")); + } + else + { + msg.AddMarkup(Loc.GetString("damage-examine-type", ("type", type))); + } + + foreach (var damage in damageSpecifier.DamageDict) + { + msg.PushNewline(); + msg.AddMarkup(Loc.GetString("damage-value", ("type", damage.Key), ("amount", damage.Value))); + } + + return msg; + } + /// /// Initialize a damageable component /// diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Cartridges.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Cartridges.cs index e1f7c9da8f..6d2c6163e3 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Cartridges.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Cartridges.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Weapons.Ranged.Systems; public abstract partial class SharedGunSystem { - private void InitializeCartridge() + protected virtual void InitializeCartridge() { SubscribeLocalEvent(OnCartridgeGetState); SubscribeLocalEvent(OnCartridgeHandleState); diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 59ba754d42..0294e1ca42 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -42,6 +42,7 @@ public abstract partial class SharedGunSystem : EntitySystem [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; [Dependency] private readonly SharedCombatModeSystem _combatMode = default!; [Dependency] protected readonly SharedContainerSystem Containers = default!; + [Dependency] protected readonly ExamineSystemShared Examine = default!; [Dependency] protected readonly SharedPhysicsSystem Physics = default!; [Dependency] protected readonly SharedPopupSystem PopupSystem = default!; [Dependency] protected readonly ThrowingSystem ThrowingSystem = default!; diff --git a/Resources/Locale/en-US/damage/damage-examine.ftl b/Resources/Locale/en-US/damage/damage-examine.ftl new file mode 100644 index 0000000000..12e885a78a --- /dev/null +++ b/Resources/Locale/en-US/damage/damage-examine.ftl @@ -0,0 +1,10 @@ +# Damage examines +damage-examinable-verb-text = Damage +damage-examinable-verb-message = Examine the damage values. + +damage-hitscan = hitscan +damage-projectile = projectile +damage-melee = melee +damage-examine = It does the following damage: +damage-examine-type = It does the following {$type} damage: +damage-value = - [color=red]{$amount}[/color] units of [color=yellow]{$type}[/color]. diff --git a/Resources/Locale/en-US/weapons/ranged/gun.ftl b/Resources/Locale/en-US/weapons/ranged/gun.ftl index 4f422a357d..d838b2baab 100644 --- a/Resources/Locale/en-US/weapons/ranged/gun.ftl +++ b/Resources/Locale/en-US/weapons/ranged/gun.ftl @@ -15,8 +15,12 @@ gun-ballistic-cycle = Cycle gun-ballistic-cycled = Cycled gun-ballistic-cycled-empty = Cycled (empty) +# CartridgeAmmo +gun-cartridge-spent = It is [color=red]spent[/color]. +gun-cartridge-unspent = It is [color=lime]not spent[/color]. + # BatteryAmmoProvider -gun-battery-examine = It has enough charge for [color={$color}]{$count} shots. +gun-battery-examine = It has enough charge for [color={$color}]{$count}[/color] shots. # MagazineAmmoProvider gun-magazine-examine = It has [color={$color}]{$count}[/color] shots remaining.