using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Timing; namespace Content.Shared._Horizon.DualWield; public sealed class DualWieldSystem : EntitySystem { [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedGunSystem _gunSystem = default!; [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnGunShot); SubscribeLocalEvent(OnEquip); SubscribeLocalEvent(OnUnequip); SubscribeLocalEvent(OnRefreshModifiers); } private void OnShutdown(Entity entity, ref ComponentShutdown args) { if (entity.Comp.LinkedWeapon is { } linked && TryComp(linked, out var linkedDual)) linkedDual.LinkedWeapon = null; } private void OnGunShot(Entity entity, ref ShotAttemptedEvent args) { if (!_timing.IsFirstTimePredicted || args.Cancelled) return; var comp = entity.Comp; if (comp.LinkedWeapon is not { } linkedWeapon || !HasComp(linkedWeapon) || !TryComp(entity, out var mainGun) || !TryComp(linkedWeapon, out var linkedGun)) return; if (_hands.GetActiveItem(args.User) != entity.Owner) return; if (mainGun.ShootCoordinates == null) return; // Synchronize targeting data at runtime to prevent issues later linkedGun.Target = mainGun.Target; var user = args.User; var shootCoordinates = mainGun.ShootCoordinates.Value; if (linkedGun.SelectedMode != SelectiveFire.FullAuto || mainGun.SelectedMode != SelectiveFire.FullAuto) { if (mainGun.ShotCounter >= 1) return; } Timer.Spawn( duration: TimeSpan.FromSeconds(comp.FireDelay), onFired: () => { if (!Exists(linkedWeapon) || !Exists(user)) return; if (!TryComp(linkedWeapon, out var currentLinkedGun)) return; if (_hands.GetActiveItem(user) != entity.Owner || !_hands.IsHolding(user, linkedWeapon)) return; if (!TryComp(linkedWeapon, out var linkedDual) || linkedDual.LinkedWeapon != entity.Owner || !TryComp(entity.Owner, out var mainDual) || mainDual.LinkedWeapon != linkedWeapon) return; _gunSystem.AttemptShoot(user, linkedWeapon, currentLinkedGun, shootCoordinates); }); } private void OnEquip(Entity entity, ref GotEquippedHandEvent args) { if (!TryComp(args.User, out var hands) || hands.Count != 2) return; foreach (var heldEntity in _hands.EnumerateHeld(args.User, hands)) { if (heldEntity == entity.Owner || !TryComp(heldEntity, out var otherDualComp)) continue; if (entity.Comp.LinkedWeapon != null || otherDualComp.LinkedWeapon != null) continue; entity.Comp.LinkedWeapon = heldEntity; otherDualComp.LinkedWeapon = entity.Owner; Dirty(heldEntity, otherDualComp); Dirty(entity, entity.Comp); _gunSystem.RefreshModifiers(entity.Owner); _gunSystem.RefreshModifiers(heldEntity); break; } } private void OnUnequip(Entity entity, ref GotUnequippedHandEvent args) { var comp = entity.Comp; if (comp.LinkedWeapon is not { } linked || !TryComp(linked, out var linkedDual)) return; comp.LinkedWeapon = null; linkedDual.LinkedWeapon = null; _gunSystem.RefreshModifiers(linked); _gunSystem.RefreshModifiers(entity.Owner); Dirty(linked, linkedDual); Dirty(entity, comp); } private void OnRefreshModifiers(Entity entity, ref GunRefreshModifiersEvent args) { var comp = entity.Comp; if (comp.LinkedWeapon != null) { args.MaxAngle *= comp.SpreadMultiplier; args.MinAngle *= comp.SpreadMultiplier; } } }