using Content.Shared._RMC14.Attachable.Systems; using Content.Shared._RMC14.Input; using Content.Shared.Examine; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Wieldable; using Content.Shared.Wieldable.Components; using Robust.Shared.Input.Binding; namespace Content.Shared._RMC14.Weapons.Ranged; public sealed class RMCSelectiveFireSystem : EntitySystem { [Dependency] private readonly SharedGunSystem _gunSystem = default!; private const string scatterExamineColour = "yellow"; private const SelectiveFire allFireModes = SelectiveFire.SemiAuto | SelectiveFire.Burst | SelectiveFire.FullAuto; public override void Initialize() { SubscribeAllEvent(OnStopShootRequest); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(SelectiveFireRefreshWield, after: new[] { typeof(AttachableHolderSystem) }); SubscribeLocalEvent(SelectiveFireRefreshWield, after: new[] { typeof(AttachableHolderSystem) }); SubscribeLocalEvent(OnSelectiveFireMapInit); SubscribeLocalEvent(OnSelectiveFireModeChanged); CommandBinds.Builder .Bind(CMKeyFunctions.RMCCycleFireMode, InputCmdHandler.FromDelegate(session => { if (session?.AttachedEntity is { } userUid && _gunSystem.TryGetGun(userUid, out var gunUid, out var gunComponent)) { _gunSystem.CycleFire(gunUid, gunComponent, userUid); } }, handle: false)) .Register(); } private void OnStopShootRequest(RequestStopShootEvent ev, EntitySessionEventArgs args) { var gunUid = GetEntity(ev.Gun); if (args.SenderSession.AttachedEntity == null || !TryComp(gunUid, out GunComponent? gunComponent) || !_gunSystem.TryGetGun(args.SenderSession.AttachedEntity.Value, out _, out var userGun)) { return; } if (userGun != gunComponent) return; gunComponent.CurrentAngle = gunComponent.MinAngleModified; Dirty(gunUid, gunComponent); } private void OnExamine(Entity gun, ref ExaminedEvent args) { if (!args.IsInDetailsRange || !TryComp(gun.Owner, out GunComponent? gunComponent)) return; using (args.PushGroup(nameof(RMCSelectiveFireComponent))) { args.PushMarkup(Loc.GetString("rmc-examine-text-scatter-max", ("colour", scatterExamineColour), ("scatter", gunComponent.MaxAngleModified.Degrees))); args.PushMarkup(Loc.GetString("rmc-examine-text-scatter-min", ("colour", scatterExamineColour), ("scatter", gunComponent.MinAngleModified.Degrees))); if (ContainsMods(gun, gunComponent.SelectedMode)) { var mods = gun.Comp.Modifiers[gunComponent.SelectedMode]; if (mods.ShotsToMaxScatter != null) args.PushMarkup(Loc.GetString("rmc-examine-text-shots-to-max-scatter", ("colour", scatterExamineColour), ("shots", mods.ShotsToMaxScatter))); } } } #region Refresh calls private void OnSelectiveFireMapInit(Entity gun, ref MapInitEvent args) { gun.Comp.BurstScatterMultModified = gun.Comp.BurstScatterMult; RefreshFireModes((gun.Owner, gun.Comp), true); } private void OnSelectiveFireModeChanged(Entity gun, ref RMCFireModeChangedEvent args) { RefreshFireModeGunValues(gun); } private void SelectiveFireRefreshWield(Entity gun, ref T args) where T : notnull { RefreshWieldableFireModeValues(gun); } #endregion #region Refresh methods public void RefreshFireModeGunValues(Entity gun) { if (!TryComp(gun.Owner, out GunComponent? gunComponent)) return; gunComponent.AngleIncrease = gun.Comp.ScatterIncrease; gunComponent.AngleDecay = gun.Comp.ScatterDecay; var ev = new GunGetFireRateEvent(gunComponent.SelectedMode == SelectiveFire.Burst ? gun.Comp.BaseFireRate * 2 : gun.Comp.BaseFireRate); RaiseLocalEvent(gun, ref ev); gunComponent.FireRate = ev.FireRate; if (ContainsMods(gun, gunComponent.SelectedMode)) { var mods = gun.Comp.Modifiers[gunComponent.SelectedMode]; ev = new GunGetFireRateEvent(1f / (1f / gunComponent.FireRate + mods.FireDelay)); RaiseLocalEvent(gun, ref ev); gunComponent.FireRate = ev.FireRate; } RefreshWieldableFireModeValues(gun); } public bool ContainsMods(Entity gun, SelectiveFire mode) { return gun.Comp.Modifiers.ContainsKey(mode); } public void RefreshWieldableFireModeValues(Entity gun) { if (!TryComp(gun.Owner, out GunComponent? gunComponent)) return; bool wielded = TryComp(gun.Owner, out WieldableComponent? wieldableComponent) && wieldableComponent.Wielded; gunComponent.CameraRecoilScalar = wielded ? gun.Comp.RecoilWielded : gun.Comp.RecoilUnwielded; gunComponent.MinAngle = wielded ? gun.Comp.ScatterWielded : gun.Comp.ScatterUnwielded; gunComponent.MaxAngle = gunComponent.MinAngle; RefreshBurstScatter((gun.Owner, gun.Comp)); _gunSystem.RefreshModifiers(gun.Owner); gunComponent.CurrentAngle = gunComponent.MinAngleModified; } public void RefreshFireModes(Entity gun, bool forceValueRefresh = false) { if (gun.Comp == null && !TryComp(gun.Owner, out gun.Comp) || !TryComp(gun.Owner, out GunComponent? gunComponent)) return; var initialMode = gunComponent.SelectedMode; var ev = new GetFireModesEvent(gun.Comp.BaseFireModes); RaiseLocalEvent(gun.Owner, ref ev); SetFireModes((gun.Owner, gunComponent), ev.Modes, !(forceValueRefresh || initialMode != gunComponent.SelectedMode)); if (TryComp(gun, out GunComponent? gunComp) && (gunComp.AvailableModes & ev.Set) != SelectiveFire.Invalid) { _gunSystem.SelectFire(gun, gunComponent, ev.Set); } if (forceValueRefresh || initialMode != gunComponent.SelectedMode) RefreshFireModeGunValues((gun.Owner, gun.Comp)); } public void RefreshModifiableFireModeValues(Entity gun) { if (gun.Comp == null && !TryComp(gun.Owner, out gun.Comp)) return; var ev = new GetFireModeValuesEvent(gun.Comp.BurstScatterMult); RaiseLocalEvent(gun.Owner, ref ev); gun.Comp.BurstScatterMultModified = ev.BurstScatterMult; RefreshWieldableFireModeValues((gun.Owner, gun.Comp)); } private void RefreshBurstScatter(Entity gun) { if (!TryComp(gun.Owner, out GunComponent? gunComponent)) return; bool wielded = TryComp(gun.Owner, out WieldableComponent? wieldableComponent) && wieldableComponent.Wielded; if (ContainsMods(gun, gunComponent.SelectedMode)) { var mods = gun.Comp.Modifiers[gunComponent.SelectedMode]; var mult = mods.UseBurstScatterMult ? gun.Comp.BurstScatterMultModified : 1.0; gunComponent.MaxAngle = wielded ? Angle.FromDegrees(Math.Max(gunComponent.MinAngle.Degrees + mods.MaxScatterModifier * mult, gunComponent.MinAngle.Degrees)) : Angle.FromDegrees(Math.Max(gunComponent.MinAngle.Degrees + mods.MaxScatterModifier * mult * mods.UnwieldedScatterMultiplier, gunComponent.MinAngle.Degrees)); if (mods.ShotsToMaxScatter != null) gunComponent.AngleIncrease = new Angle(((double)(gunComponent.MaxAngle - gunComponent.MinAngle)) / mods.ShotsToMaxScatter.Value); } } #endregion #region Firemode changes public void AddFireMode(Entity gun, SelectiveFire newMode) { if (gun.Comp == null && !TryComp(gun.Owner, out gun.Comp)) return; gun.Comp.AvailableModes |= newMode; Dirty(gun); } public void SetFireModes(Entity gun, SelectiveFire modes, bool dirty = true) { if (gun.Comp == null && !TryComp(gun.Owner, out gun.Comp) || (modes & allFireModes) == SelectiveFire.Invalid) return; gun.Comp.AvailableModes = allFireModes; while ((gun.Comp.SelectedMode & modes) != gun.Comp.SelectedMode) _gunSystem.CycleFire(gun.Owner, gun.Comp); gun.Comp.AvailableModes = modes; if (!dirty) return; Dirty(gun); } #endregion }