| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- using Content.Shared.DoAfter;
- using Content.Shared.Examine;
- using Content.Shared.Interaction;
- using Content.Shared.Interaction.Events;
- using Content.Shared.Verbs;
- using Content.Shared.Weapons.Ranged.Components;
- using Content.Shared.Weapons.Ranged.Events;
- using Robust.Shared.Containers;
- using Robust.Shared.Map;
- using Robust.Shared.Serialization;
- using Content.Shared._RMC14.Weapons.Ranged;
- namespace Content.Shared.Weapons.Ranged.Systems;
- public abstract partial class SharedGunSystem
- {
- [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
- [Dependency] private readonly SharedInteractionSystem _interaction = default!;
- protected virtual void InitializeBallistic()
- {
- SubscribeLocalEvent<BallisticAmmoProviderComponent, ComponentInit>(OnBallisticInit);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, MapInitEvent>(OnBallisticMapInit);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, TakeAmmoEvent>(OnBallisticTakeAmmo);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, GetAmmoCountEvent>(OnBallisticAmmoCount);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, ExaminedEvent>(OnBallisticExamine);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, GetVerbsEvent<Verb>>(OnBallisticVerb);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, InteractUsingEvent>(OnBallisticInteractUsing);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, AfterInteractEvent>(OnBallisticAfterInteract);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, AmmoFillDoAfterEvent>(OnBallisticAmmoFillDoAfter);
- SubscribeLocalEvent<BallisticAmmoProviderComponent, UseInHandEvent>(OnBallisticUse);
- }
- private void OnBallisticUse(EntityUid uid, BallisticAmmoProviderComponent component, UseInHandEvent args)
- {
- if (args.Handled)
- return;
- ManualCycle(uid, component, TransformSystem.GetMapCoordinates(uid), args.User);
- args.Handled = true;
- }
- private void OnBallisticInteractUsing(EntityUid uid, BallisticAmmoProviderComponent component, InteractUsingEvent args)
- {
- if (args.Handled)
- return;
- if (_whitelistSystem.IsWhitelistFailOrNull(component.Whitelist, args.Used))
- return;
- if (GetBallisticShots(component) >= component.Capacity)
- return;
- component.Entities.Add(args.Used);
- Containers.Insert(args.Used, component.Container);
- // Not predicted so
- Audio.PlayPredicted(component.SoundInsert, uid, args.User);
- args.Handled = true;
- UpdateBallisticAppearance(uid, component);
- DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities));
- }
- private void OnBallisticAfterInteract(EntityUid uid, BallisticAmmoProviderComponent component, AfterInteractEvent args)
- {
- if (args.Handled ||
- !component.MayTransfer ||
- !Timing.IsFirstTimePredicted ||
- args.Target == null ||
- args.Used == args.Target ||
- Deleted(args.Target) ||
- !TryComp<BallisticAmmoProviderComponent>(args.Target, out var targetComponent) ||
- targetComponent.Whitelist == null)
- {
- return;
- }
- args.Handled = true;
- // Continuous loading
- _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.FillDelay, new AmmoFillDoAfterEvent(), used: uid, target: args.Target, eventTarget: uid)
- {
- BreakOnMove = true,
- BreakOnDamage = false,
- NeedHand = true,
- });
- }
- private void OnBallisticAmmoFillDoAfter(EntityUid uid, BallisticAmmoProviderComponent component, AmmoFillDoAfterEvent args)
- {
- if (args.Handled || args.Cancelled)
- return;
- if (Deleted(args.Target) ||
- !TryComp<BallisticAmmoProviderComponent>(args.Target, out var target) ||
- target.Whitelist == null)
- return;
- if (target.Entities.Count + target.UnspawnedCount == target.Capacity)
- {
- Popup(
- Loc.GetString("gun-ballistic-transfer-target-full",
- ("entity", args.Target)),
- args.Target,
- args.User);
- return;
- }
- if (component.Entities.Count + component.UnspawnedCount == 0)
- {
- Popup(
- Loc.GetString("gun-ballistic-transfer-empty",
- ("entity", uid)),
- uid,
- args.User);
- return;
- }
- void SimulateInsertAmmo(EntityUid ammo, EntityUid ammoProvider, EntityCoordinates coordinates)
- {
- // We call SharedInteractionSystem to raise contact events. Checks are already done by this point.
- _interaction.InteractUsing(args.User, ammo, ammoProvider, coordinates, checkCanInteract: false, checkCanUse: false);
- }
- List<(EntityUid? Entity, IShootable Shootable)> ammo = new();
- var evTakeAmmo = new TakeAmmoEvent(1, ammo, Transform(uid).Coordinates, args.User);
- RaiseLocalEvent(uid, evTakeAmmo);
- foreach (var (ent, _) in ammo)
- {
- if (ent == null)
- continue;
- if (_whitelistSystem.IsWhitelistFail(target.Whitelist, ent.Value))
- {
- Popup(
- Loc.GetString("gun-ballistic-transfer-invalid",
- ("ammoEntity", ent.Value),
- ("targetEntity", args.Target.Value)),
- uid,
- args.User);
- SimulateInsertAmmo(ent.Value, uid, Transform(uid).Coordinates);
- }
- else
- {
- // play sound to be cool
- Audio.PlayPredicted(component.SoundInsert, uid, args.User);
- SimulateInsertAmmo(ent.Value, args.Target.Value, Transform(args.Target.Value).Coordinates);
- }
- if (IsClientSide(ent.Value))
- Del(ent.Value);
- }
- // repeat if there is more space in the target and more ammo to fill it
- var moreSpace = target.Entities.Count + target.UnspawnedCount < target.Capacity;
- var moreAmmo = component.Entities.Count + component.UnspawnedCount > 0;
- args.Repeat = moreSpace && moreAmmo;
- }
- private void OnBallisticVerb(EntityUid uid, BallisticAmmoProviderComponent component, GetVerbsEvent<Verb> args)
- {
- if (!args.CanAccess || !args.CanInteract || args.Hands == null || !component.Cycleable)
- return;
- if (component.Cycleable)
- {
- args.Verbs.Add(new Verb()
- {
- Text = Loc.GetString("gun-ballistic-cycle"),
- Disabled = GetBallisticShots(component) == 0,
- Act = () => ManualCycle(uid, component, TransformSystem.GetMapCoordinates(uid), args.User),
- });
- }
- }
- private void OnBallisticExamine(EntityUid uid, BallisticAmmoProviderComponent component, ExaminedEvent args)
- {
- if (!args.IsInDetailsRange)
- return;
- args.PushMarkup(Loc.GetString("gun-magazine-examine", ("color", AmmoExamineColor), ("count", GetBallisticShots(component))));
- }
- private void ManualCycle(EntityUid uid, BallisticAmmoProviderComponent component, MapCoordinates coordinates, EntityUid? user = null, GunComponent? gunComp = null)
- {
- if (!component.Cycleable)
- return;
- // Reset shotting for cycling
- if (Resolve(uid, ref gunComp, false) &&
- gunComp is { FireRateModified: > 0f } &&
- !Paused(uid))
- {
- gunComp.NextFire = Timing.CurTime + TimeSpan.FromSeconds(1 / gunComp.FireRateModified);
- DirtyField(uid, gunComp, nameof(GunComponent.NextFire));
- }
- Audio.PlayPredicted(component.SoundRack, uid, user);
- var shots = GetBallisticShots(component);
- Cycle(uid, component, coordinates);
- var text = Loc.GetString(shots == 0 ? "gun-ballistic-cycled-empty" : "gun-ballistic-cycled");
- Popup(text, uid, user);
- UpdateBallisticAppearance(uid, component);
- UpdateAmmoCount(uid);
- }
- protected abstract void Cycle(EntityUid uid, BallisticAmmoProviderComponent component, MapCoordinates coordinates);
- private void OnBallisticInit(EntityUid uid, BallisticAmmoProviderComponent component, ComponentInit args)
- {
- component.Container = Containers.EnsureContainer<Container>(uid, "ballistic-ammo");
- // TODO: This is called twice though we need to support loading appearance data (and we need to call it on MapInit
- // to ensure it's correct).
- UpdateBallisticAppearance(uid, component);
- }
- private void OnBallisticMapInit(EntityUid uid, BallisticAmmoProviderComponent component, MapInitEvent args)
- {
- // TODO this should be part of the prototype, not set on map init.
- // Alternatively, just track spawned count, instead of unspawned count.
- if (component.Proto != null)
- {
- component.UnspawnedCount = Math.Max(0, component.Capacity - component.Container.ContainedEntities.Count);
- UpdateBallisticAppearance(uid, component);
- DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount));
- }
- }
- protected int GetBallisticShots(BallisticAmmoProviderComponent component)
- {
- return component.Entities.Count + component.UnspawnedCount;
- }
- private void OnBallisticTakeAmmo(EntityUid uid, BallisticAmmoProviderComponent component, TakeAmmoEvent args)
- {
- for (var i = 0; i < args.Shots; i++)
- {
- EntityUid entity;
- if (component.Entities.Count > 0)
- {
- entity = component.Entities[^1];
- args.Ammo.Add((entity, EnsureShootable(entity)));
- component.Entities.RemoveAt(component.Entities.Count - 1);
- DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities));
- Containers.Remove(entity, component.Container);
- }
- else if (component.UnspawnedCount > 0)
- {
- component.UnspawnedCount--;
- DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount));
- entity = Spawn(component.Proto, args.Coordinates);
- args.Ammo.Add((entity, EnsureShootable(entity)));
- }
- }
- UpdateBallisticAppearance(uid, component);
- }
- private void OnBallisticAmmoCount(EntityUid uid, BallisticAmmoProviderComponent component, ref GetAmmoCountEvent args)
- {
- args.Count = GetBallisticShots(component);
- args.Capacity = component.Capacity;
- }
- public void UpdateBallisticAppearance(EntityUid uid, BallisticAmmoProviderComponent component)
- {
- if (!Timing.IsFirstTimePredicted || !TryComp<AppearanceComponent>(uid, out var appearance))
- return;
- Appearance.SetData(uid, AmmoVisuals.AmmoCount, GetBallisticShots(component), appearance);
- Appearance.SetData(uid, AmmoVisuals.AmmoMax, component.Capacity, appearance);
- }
- public void SetBallisticUnspawned(Entity<BallisticAmmoProviderComponent> entity, int count)
- {
- if (entity.Comp.UnspawnedCount == count)
- return;
- entity.Comp.UnspawnedCount = count;
- UpdateBallisticAppearance(entity.Owner, entity.Comp);
- UpdateAmmoCount(entity.Owner);
- Dirty(entity);
- }
- }
- /// <summary>
- /// DoAfter event for filling one ballistic ammo provider from another.
- /// </summary>
- [Serializable, NetSerializable]
- public sealed partial class AmmoFillDoAfterEvent : SimpleDoAfterEvent
- {
- }
|