| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- using System.Numerics;
- using Content.Shared.CombatMode;
- using Content.Shared.Hands;
- using Content.Shared.Hands.Components;
- using Content.Shared.Interaction;
- using Content.Shared.Movement.Events;
- using Content.Shared.Physics;
- using Content.Shared.Projectiles;
- using Content.Shared.Weapons.Ranged.Components;
- using Content.Shared.Weapons.Ranged.Systems;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Network;
- using Robust.Shared.Physics;
- using Robust.Shared.Physics.Components;
- using Robust.Shared.Physics.Dynamics.Joints;
- using Robust.Shared.Physics.Systems;
- using Robust.Shared.Serialization;
- using Robust.Shared.Timing;
- namespace Content.Shared.Weapons.Misc;
- public abstract class SharedGrapplingGunSystem : EntitySystem
- {
- [Dependency] protected readonly IGameTiming Timing = default!;
- [Dependency] private readonly INetManager _netManager = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedJointSystem _joints = default!;
- [Dependency] private readonly SharedGunSystem _gun = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
- public const string GrapplingJoint = "grappling";
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<GrapplingProjectileComponent, ProjectileEmbedEvent>(OnGrappleCollide);
- SubscribeLocalEvent<GrapplingProjectileComponent, JointRemovedEvent>(OnGrappleJointRemoved);
- SubscribeLocalEvent<CanWeightlessMoveEvent>(OnWeightlessMove);
- SubscribeAllEvent<RequestGrapplingReelMessage>(OnGrapplingReel);
- SubscribeLocalEvent<GrapplingGunComponent, GunShotEvent>(OnGrapplingShot);
- SubscribeLocalEvent<GrapplingGunComponent, ActivateInWorldEvent>(OnGunActivate);
- SubscribeLocalEvent<GrapplingGunComponent, HandDeselectedEvent>(OnGrapplingDeselected);
- }
- private void OnGrappleJointRemoved(EntityUid uid, GrapplingProjectileComponent component, JointRemovedEvent args)
- {
- if (_netManager.IsServer)
- QueueDel(uid);
- }
- private void OnGrapplingShot(EntityUid uid, GrapplingGunComponent component, ref GunShotEvent args)
- {
- foreach (var (shotUid, _) in args.Ammo)
- {
- if (!HasComp<GrapplingProjectileComponent>(shotUid))
- continue;
- //todo: this doesn't actually support multigrapple
- // At least show the visuals.
- component.Projectile = shotUid.Value;
- Dirty(uid, component);
- var visuals = EnsureComp<JointVisualsComponent>(shotUid.Value);
- visuals.Sprite = component.RopeSprite;
- visuals.OffsetA = new Vector2(0f, 0.5f);
- visuals.Target = GetNetEntity(uid);
- Dirty(shotUid.Value, visuals);
- }
- TryComp<AppearanceComponent>(uid, out var appearance);
- _appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, false, appearance);
- Dirty(uid, component);
- }
- private void OnGrapplingDeselected(EntityUid uid, GrapplingGunComponent component, HandDeselectedEvent args)
- {
- SetReeling(uid, component, false, args.User);
- }
- private void OnGrapplingReel(RequestGrapplingReelMessage msg, EntitySessionEventArgs args)
- {
- var player = args.SenderSession.AttachedEntity;
- if (!TryComp<HandsComponent>(player, out var hands) ||
- !TryComp<GrapplingGunComponent>(hands.ActiveHandEntity, out var grappling))
- {
- return;
- }
- if (msg.Reeling &&
- (!TryComp<CombatModeComponent>(player, out var combatMode) ||
- !combatMode.IsInCombatMode))
- {
- return;
- }
- SetReeling(hands.ActiveHandEntity.Value, grappling, msg.Reeling, player.Value);
- }
- private void OnWeightlessMove(ref CanWeightlessMoveEvent ev)
- {
- if (ev.CanMove || !TryComp<JointRelayTargetComponent>(ev.Uid, out var relayComp))
- return;
- foreach (var relay in relayComp.Relayed)
- {
- if (TryComp<JointComponent>(relay, out var jointRelay) && jointRelay.GetJoints.ContainsKey(GrapplingJoint))
- {
- ev.CanMove = true;
- return;
- }
- }
- }
- private void OnGunActivate(EntityUid uid, GrapplingGunComponent component, ActivateInWorldEvent args)
- {
- if (!Timing.IsFirstTimePredicted || args.Handled || !args.Complex || component.Projectile is not {} projectile)
- return;
- _audio.PlayPredicted(component.CycleSound, uid, args.User);
- _appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, true);
- if (_netManager.IsServer)
- QueueDel(projectile);
- component.Projectile = null;
- SetReeling(uid, component, false, args.User);
- _gun.ChangeBasicEntityAmmoCount(uid, 1);
- args.Handled = true;
- }
- private void SetReeling(EntityUid uid, GrapplingGunComponent component, bool value, EntityUid? user)
- {
- if (component.Reeling == value)
- return;
- if (value)
- {
- if (Timing.IsFirstTimePredicted)
- component.Stream = _audio.PlayPredicted(component.ReelSound, uid, user)?.Entity;
- }
- else
- {
- if (Timing.IsFirstTimePredicted)
- {
- component.Stream = _audio.Stop(component.Stream);
- }
- }
- component.Reeling = value;
- Dirty(uid, component);
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var query = EntityQueryEnumerator<GrapplingGunComponent>();
- while (query.MoveNext(out var uid, out var grappling))
- {
- if (!grappling.Reeling)
- {
- if (Timing.IsFirstTimePredicted)
- {
- // Just in case.
- grappling.Stream = _audio.Stop(grappling.Stream);
- }
- continue;
- }
- if (!TryComp<JointComponent>(uid, out var jointComp) ||
- !jointComp.GetJoints.TryGetValue(GrapplingJoint, out var joint) ||
- joint is not DistanceJoint distance)
- {
- SetReeling(uid, grappling, false, null);
- continue;
- }
- // TODO: This should be on engine.
- distance.MaxLength = MathF.Max(distance.MinLength, distance.MaxLength - grappling.ReelRate * frameTime);
- distance.Length = MathF.Min(distance.MaxLength, distance.Length);
- _physics.WakeBody(joint.BodyAUid);
- _physics.WakeBody(joint.BodyBUid);
- if (jointComp.Relay != null)
- {
- _physics.WakeBody(jointComp.Relay.Value);
- }
- Dirty(uid, jointComp);
- if (distance.MaxLength.Equals(distance.MinLength))
- {
- SetReeling(uid, grappling, false, null);
- }
- }
- }
- private void OnGrappleCollide(EntityUid uid, GrapplingProjectileComponent component, ref ProjectileEmbedEvent args)
- {
- if (!Timing.IsFirstTimePredicted)
- return;
- var jointComp = EnsureComp<JointComponent>(uid);
- var joint = _joints.CreateDistanceJoint(uid, args.Weapon, anchorA: new Vector2(0f, 0.5f), id: GrapplingJoint);
- joint.MaxLength = joint.Length + 0.2f;
- joint.Stiffness = 1f;
- joint.MinLength = 0.35f;
- // Setting velocity directly for mob movement fucks this so need to make them aware of it.
- // joint.Breakpoint = 4000f;
- Dirty(uid, jointComp);
- }
- [Serializable, NetSerializable]
- protected sealed class RequestGrapplingReelMessage : EntityEventArgs
- {
- public bool Reeling;
- public RequestGrapplingReelMessage(bool reeling)
- {
- Reeling = reeling;
- }
- }
- }
|