--- /dev/null
+using System.Linq;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Popups;
+using Content.Shared.Projectiles;
+using Content.Shared.Tag;
+using Content.Shared.Weapons.Ranged.Events;
+using Content.Shared.Weapons.Ranged.Systems;
+using Content.Shared.Weapons.Ranged.Upgrades.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Weapons.Ranged.Upgrades;
+
+public sealed class GunUpgradeSystem : EntitySystem
+{
+ [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedGunSystem _gun = default!;
+ [Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ /// <inheritdoc/>
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<UpgradeableGunComponent, ComponentInit>(OnInit);
+ SubscribeLocalEvent<UpgradeableGunComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
+ SubscribeLocalEvent<UpgradeableGunComponent, ExaminedEvent>(OnExamine);
+
+ SubscribeLocalEvent<UpgradeableGunComponent, GunRefreshModifiersEvent>(RelayEvent);
+ SubscribeLocalEvent<UpgradeableGunComponent, GunShotEvent>(RelayEvent);
+
+ SubscribeLocalEvent<GunUpgradeFireRateComponent, GunRefreshModifiersEvent>(OnFireRateRefresh);
+ SubscribeLocalEvent<GunUpgradeSpeedComponent, GunRefreshModifiersEvent>(OnSpeedRefresh);
+ SubscribeLocalEvent<GunUpgradeDamageComponent, GunShotEvent>(OnDamageGunShot);
+ }
+
+ private void RelayEvent<T>(Entity<UpgradeableGunComponent> ent, ref T args) where T : notnull
+ {
+ foreach (var upgrade in GetCurrentUpgrades(ent))
+ {
+ RaiseLocalEvent(upgrade, ref args);
+ }
+ }
+
+ private void OnExamine(Entity<UpgradeableGunComponent> ent, ref ExaminedEvent args)
+ {
+ using (args.PushGroup(nameof(UpgradeableGunComponent)))
+ {
+ foreach (var upgrade in GetCurrentUpgrades(ent))
+ {
+ args.PushMarkup(Loc.GetString(upgrade.Comp.ExamineText));
+ }
+ }
+ }
+
+ private void OnInit(Entity<UpgradeableGunComponent> ent, ref ComponentInit args)
+ {
+ _container.EnsureContainer<Container>(ent, ent.Comp.UpgradesContainerId);
+ }
+
+ private void OnAfterInteractUsing(Entity<UpgradeableGunComponent> ent, ref AfterInteractUsingEvent args)
+ {
+ if (args.Handled || !args.CanReach || !TryComp<GunUpgradeComponent>(args.Used, out var upgradeComponent))
+ return;
+
+ if (GetCurrentUpgrades(ent).Count >= ent.Comp.MaxUpgradeCount)
+ {
+ _popup.PopupPredicted(Loc.GetString("upgradeable-gun-popup-upgrade-limit"), ent, args.User);
+ return;
+ }
+
+ if (_entityWhitelist.IsWhitelistFail(ent.Comp.Whitelist, args.Used))
+ return;
+
+ if (GetCurrentUpgradeTags(ent).ToHashSet().IsSupersetOf(upgradeComponent.Tags))
+ {
+ _popup.PopupPredicted(Loc.GetString("upgradeable-gun-popup-already-present"), ent, args.User);
+ return;
+ }
+
+ _audio.PlayPredicted(ent.Comp.InsertSound, ent, args.User);
+ _popup.PopupClient(Loc.GetString("gun-upgrade-popup-insert", ("upgrade", args.Used),("gun", ent.Owner)), args.User);
+ _gun.RefreshModifiers(ent.Owner);
+ args.Handled = _container.Insert(args.Used, _container.GetContainer(ent, ent.Comp.UpgradesContainerId));
+
+ _adminLog.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Used):player} inserted gun upgrade {ToPrettyString(args.Used)} into {ToPrettyString(ent.Owner)}.");
+ }
+
+ private void OnFireRateRefresh(Entity<GunUpgradeFireRateComponent> ent, ref GunRefreshModifiersEvent args)
+ {
+ args.FireRate *= ent.Comp.Coefficient;
+ }
+
+ private void OnSpeedRefresh(Entity<GunUpgradeSpeedComponent> ent, ref GunRefreshModifiersEvent args)
+ {
+ args.ProjectileSpeed *= ent.Comp.Coefficient;
+ }
+
+ private void OnDamageGunShot(Entity<GunUpgradeDamageComponent> ent, ref GunShotEvent args)
+ {
+ foreach (var (ammo, _) in args.Ammo)
+ {
+ if (TryComp<ProjectileComponent>(ammo, out var proj))
+ proj.Damage += ent.Comp.Damage;
+ }
+ }
+
+ /// <summary>
+ /// Gets the entities inside the gun's upgrade container.
+ /// </summary>
+ public HashSet<Entity<GunUpgradeComponent>> GetCurrentUpgrades(Entity<UpgradeableGunComponent> ent)
+ {
+ if (!_container.TryGetContainer(ent, ent.Comp.UpgradesContainerId, out var container))
+ return new HashSet<Entity<GunUpgradeComponent>>();
+
+ var upgrades = new HashSet<Entity<GunUpgradeComponent>>();
+ foreach (var contained in container.ContainedEntities)
+ {
+ if (TryComp<GunUpgradeComponent>(contained, out var upgradeComp))
+ upgrades.Add((contained, upgradeComp));
+ }
+
+ return upgrades;
+ }
+
+ /// <summary>
+ /// Gets the tags of the upgrades currently applied.
+ /// </summary>
+ public IEnumerable<ProtoId<TagPrototype>> GetCurrentUpgradeTags(Entity<UpgradeableGunComponent> ent)
+ {
+ foreach (var upgrade in GetCurrentUpgrades(ent))
+ {
+ foreach (var tag in upgrade.Comp.Tags)
+ {
+ yield return tag;
+ }
+ }
+ }
+}