using Content.Shared.Examine; using Content.Shared.Item; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Serialization; namespace Content.Shared.Weapons.Ranged.Systems; public abstract partial class SharedGunSystem { [Dependency] private readonly SharedItemSystem _item = default!; protected virtual void InitializeBattery() { // Trying to dump comp references hence the below // Hitscan SubscribeLocalEvent(OnBatteryGetState); SubscribeLocalEvent(OnBatteryHandleState); SubscribeLocalEvent(OnBatteryTakeAmmo); SubscribeLocalEvent(OnBatteryAmmoCount); SubscribeLocalEvent(OnBatteryExamine); // Projectile SubscribeLocalEvent(OnBatteryGetState); SubscribeLocalEvent(OnBatteryHandleState); SubscribeLocalEvent(OnBatteryTakeAmmo); SubscribeLocalEvent(OnBatteryAmmoCount); SubscribeLocalEvent(OnBatteryExamine); // TwoModeEnergy SubscribeLocalEvent(OnTwoModeInit); SubscribeLocalEvent(OnBatteryTwoModeGetState); SubscribeLocalEvent(OnBatteryTwoModeHandleState); SubscribeLocalEvent(OnBatteryTakeAmmo); SubscribeLocalEvent(OnBatteryAmmoCount); SubscribeLocalEvent(OnBatteryExamine); } private void OnTwoModeInit(EntityUid uid, TwoModeEnergyAmmoProviderComponent component, ComponentInit args) { if (!Timing.IsFirstTimePredicted || !TryComp(component.Owner, out var appearance)) return; Appearance.SetData(appearance.Owner, AmmoVisuals.InStun, component.InStun, appearance); } private void OnBatteryTwoModeHandleState( EntityUid uid, TwoModeEnergyAmmoProviderComponent component, ref ComponentHandleState args) { if (args.Current is not TwoModeComponentState state) return; component.Shots = state.Shots; component.Capacity = state.MaxShots; component.FireCost = state.FireCost; component.CurrentMode = state.CurrentMode; component.InStun = state.InStun; } private void OnBatteryTwoModeGetState( EntityUid uid, TwoModeEnergyAmmoProviderComponent component, ref ComponentGetState args) { args.State = new TwoModeComponentState() { Shots = component.Shots, MaxShots = component.Capacity, FireCost = component.FireCost, CurrentMode = component.CurrentMode, InStun = component.InStun }; } protected void UpdateTwoModeAppearance(EntityUid uid, TwoModeEnergyAmmoProviderComponent component) { if (!TryComp(uid, out var appearance)) return; if (!TryComp(uid, out var item)) return; _item.SetHeldPrefix(uid, component.InStun ? null : "laser", false, item); Appearance.SetData(uid, AmmoVisuals.InStun, component.InStun, appearance); Dirty(uid, component); } private void OnBatteryHandleState( EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentHandleState args) { if (args.Current is not BatteryAmmoProviderComponentState state) return; component.Shots = state.Shots; component.Capacity = state.MaxShots; component.FireCost = state.FireCost; } private void OnBatteryGetState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentGetState args) { args.State = new BatteryAmmoProviderComponentState() { Shots = component.Shots, MaxShots = component.Capacity, FireCost = component.FireCost, }; } private void OnBatteryExamine(EntityUid uid, BatteryAmmoProviderComponent component, ExaminedEvent args) { args.PushMarkup(Loc.GetString("gun-battery-examine", ("color", AmmoExamineColor), ("count", component.Shots))); } private void OnBatteryTakeAmmo(EntityUid uid, BatteryAmmoProviderComponent component, TakeAmmoEvent args) { var shots = Math.Min(args.Shots, component.Shots); // Don't dirty if it's an empty fire. if (shots == 0) return; for (var i = 0; i < shots; i++) { args.Ammo.Add(GetShootable(component, args.Coordinates)); component.Shots--; } TakeCharge(uid, component); UpdateBatteryAppearance(uid, component); Dirty(uid, component); } private void OnBatteryAmmoCount(EntityUid uid, BatteryAmmoProviderComponent component, ref GetAmmoCountEvent args) { args.Count = component.Shots; args.Capacity = component.Capacity; } /// /// Update the battery (server-only) whenever fired. /// protected virtual void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) { } protected void UpdateBatteryAppearance(EntityUid uid, BatteryAmmoProviderComponent component) { if (!TryComp(uid, out var appearance)) return; Appearance.SetData(uid, AmmoVisuals.HasAmmo, component.Shots != 0, appearance); Appearance.SetData(uid, AmmoVisuals.AmmoCount, component.Shots, appearance); Appearance.SetData(uid, AmmoVisuals.AmmoMax, component.Capacity, appearance); } private (EntityUid? Entity, IShootable) GetShootable( BatteryAmmoProviderComponent component, EntityCoordinates coordinates) { switch (component) { case ProjectileBatteryAmmoProviderComponent proj: var ent = Spawn(proj.Prototype, coordinates); return (ent, EnsureShootable(ent)); case HitscanBatteryAmmoProviderComponent hitscan: return (null, ProtoManager.Index(hitscan.Prototype)); case TwoModeEnergyAmmoProviderComponent twoMode: var projEntity = Spawn(twoMode.CurrentMode == EnergyModes.Stun ? twoMode.StunPrototype : twoMode.LaserPrototype, coordinates); return (projEntity, EnsureComp(projEntity)); default: throw new ArgumentOutOfRangeException(); } } [Serializable, NetSerializable] private sealed class BatteryAmmoProviderComponentState : ComponentState { public int Shots; public int MaxShots; public float FireCost; } [Serializable, NetSerializable] public sealed class TwoModeComponentState : ComponentState { public EnergyModes CurrentMode { get; init; } public int Shots; public int MaxShots; public float FireCost; public bool InStun; } }