Files
tbd-station-14/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs
2023-07-06 13:39:34 +10:00

237 lines
8.0 KiB
C#

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.Timing;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.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;
using Robust.Shared.Utility;
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 SharedPhysicsSystem _physics = default!;
[Dependency] private readonly UseDelaySystem _delay = default!;
public const string GrapplingJoint = "grappling";
public const float ReelRate = 2.5f;
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)
{
QueueDel(uid);
}
private void OnGrapplingShot(EntityUid uid, GrapplingGunComponent component, ref GunShotEvent args)
{
foreach (var (shotUid, _) in args.Ammo)
{
if (!HasComp<GrapplingProjectileComponent>(shotUid))
continue;
// At least show the visuals.
component.Projectile = shotUid.Value;
Dirty(component);
var visuals = EnsureComp<JointVisualsComponent>(shotUid.Value);
visuals.Sprite =
new SpriteSpecifier.Rsi(new ResPath("Objects/Weapons/Guns/Launchers/grappling_gun.rsi"), "rope");
visuals.OffsetA = new Vector2(0f, 0.5f);
visuals.Target = uid;
Dirty(visuals);
}
TryComp<AppearanceComponent>(uid, out var appearance);
_appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, false, appearance);
}
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 || _delay.ActiveDelay(uid))
return;
_delay.BeginDelay(uid);
_audio.PlayPredicted(component.CycleSound, uid, args.User);
TryComp<AppearanceComponent>(uid, out var appearance);
_appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, true, appearance);
SetReeling(uid, component, false, args.User);
if (!Deleted(component.Projectile))
{
if (_netManager.IsServer)
{
QueueDel(component.Projectile.Value);
}
component.Projectile = null;
Dirty(component);
}
}
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);
}
else
{
if (Timing.IsFirstTimePredicted)
{
component.Stream?.Stop();
component.Stream = null;
}
}
component.Reeling = value;
Dirty(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?.Stop();
grappling.Stream = null;
}
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 - 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(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(jointComp);
}
[Serializable, NetSerializable]
protected sealed class RequestGrapplingReelMessage : EntityEventArgs
{
public bool Reeling;
public RequestGrapplingReelMessage(bool reeling)
{
Reeling = reeling;
}
}
}