Skip to content

Commit

Permalink
Merge pull request #1482 from Saeko-44/master
Browse files Browse the repository at this point in the history
Fishing part 1 : The Fishing Rod
  • Loading branch information
mqole authored Feb 10, 2025
2 parents 48a4313 + 5f01280 commit e0b4c5d
Show file tree
Hide file tree
Showing 33 changed files with 589 additions and 0 deletions.
58 changes: 58 additions & 0 deletions Content.Client/_Impstation/Fishing/FishingRodSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Net;
using Content.Client.Hands.Systems;
using Content.Shared._Impstation.Fishing;
using Content.Shared.CombatMode;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Input;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics.Joints;

namespace Content.Client._Impstation.Fishing;

//Imp : Basically a copy of GrapplingGunSystem
public sealed class FishingRodSystem : SharedFishingRodSystem
{
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly InputSystem _input = default!;
[Dependency] private readonly IPlayerManager _player = default!;

public override void Update(float frameTime)
{
base.Update(frameTime);

// Oh boy another input handler.
// If someone thinks of a better way to unify this please tell me.
if (!Timing.IsFirstTimePredicted)
return;

var local = _player.LocalEntity;
var handUid = _hands.GetActiveHandEntity();

if (!TryComp<FishingRodComponent>(handUid, out var grappling))
return;

if (!TryComp<JointComponent>(handUid, out var jointComp) ||
!jointComp.GetJoints.TryGetValue(GrapplingJoint, out var joint) ||
joint is not DistanceJoint distance)
{
return;
}

if (distance.MaxLength <= distance.MinLength)
return;

var reelKey = _input.CmdStates.GetState(EngineKeyFunctions.UseSecondary) == BoundKeyState.Down;

if (!TryComp<CombatModeComponent>(local, out var combatMode) ||
!combatMode.IsInCombatMode)
{
reelKey = false;
}

if (grappling.Reeling == reelKey)
return;

RaisePredictiveEvent(new RequestGrapplingReelMessage(reelKey));
}
}
9 changes: 9 additions & 0 deletions Content.Server/_Impstation/Fishing/FishingRodSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Content.Shared._Impstation.Fishing;

namespace Content.Server._Impstation.Fishing;

//Imp : Basically a copy of GrapplingGunSystem
public sealed class FishingRodSystem : SharedFishingRodSystem
{

}
10 changes: 10 additions & 0 deletions Content.Shared/_Impstation/Fishing/FishingProjectileComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Robust.Shared.GameStates;

namespace Content.Shared._Impstation.Fishing;

[RegisterComponent, NetworkedComponent]
//Imp : Basically a copy of GrapplingProjectileComponent
public sealed partial class FishingProjectileComponent : Component
{

}
41 changes: 41 additions & 0 deletions Content.Shared/_Impstation/Fishing/FishingRodComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;

namespace Content.Shared._Impstation.Fishing;

// I have tried to make this as generic as possible but "delete joint on cycle / right-click reels in" is very specific behavior.
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
//Imp : Basically a copy of GrapplingGunComponent
public sealed partial class FishingRodComponent : Component
{
/// <summary>
/// Hook's reeling force and speed - the higher the number, the faster the hook rewinds.
/// </summary>
[DataField, AutoNetworkedField]
public float ReelRate = 2.5f;

[DataField("jointId"), AutoNetworkedField]
public string Joint = string.Empty;

[DataField, AutoNetworkedField]
public EntityUid? Projectile;

[DataField, AutoNetworkedField]
public bool Reeling;

[DataField, AutoNetworkedField]
public SoundSpecifier? ReelSound = new SoundPathSpecifier("/Audio/Weapons/reel.ogg")
{
Params = AudioParams.Default.WithLoop(true)
};

[DataField, AutoNetworkedField]
public SoundSpecifier? CycleSound = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/kinetic_reload.ogg");

[DataField]
public SpriteSpecifier RopeSprite =
new SpriteSpecifier.Rsi(new ResPath("Objects/Weapons/Guns/Launchers/grappling_gun.rsi"), "rope");

public EntityUid? Stream;
}
246 changes: 246 additions & 0 deletions Content.Shared/_Impstation/Fishing/SharedFishingSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
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.Misc;
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._Impstation.Fishing;

//Imp : Basically a copy of GrapplingGunSystem
public abstract class SharedFishingRodSystem : 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<CanWeightlessMoveEvent>(OnWeightlessMove);
SubscribeAllEvent<RequestGrapplingReelMessage>(OnGrapplingReel);

SubscribeLocalEvent<FishingRodComponent, GunShotEvent>(OnGrapplingShot);
SubscribeLocalEvent<FishingRodComponent, ActivateInWorldEvent>(OnGunActivate);
SubscribeLocalEvent<FishingRodComponent, HandDeselectedEvent>(OnGrapplingDeselected);

SubscribeLocalEvent<FishingProjectileComponent, ProjectileEmbedEvent>(OnGrappleCollide);
SubscribeLocalEvent<FishingProjectileComponent, JointRemovedEvent>(OnGrappleJointRemoved);
SubscribeLocalEvent<FishingProjectileComponent, RemoveEmbedEvent>(OnRemoveEmbed);
}

private void OnGrappleJointRemoved(EntityUid uid, FishingProjectileComponent component, JointRemovedEvent args)
{
if (_netManager.IsServer)
QueueDel(uid);
}

private void OnGrapplingShot(EntityUid uid, FishingRodComponent component, ref GunShotEvent args)
{
foreach (var (shotUid, _) in args.Ammo)
{
if (!HasComp<FishingProjectileComponent>(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, FishingRodComponent 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<FishingRodComponent>(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, FishingRodComponent 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);

_joints.RemoveJoint(uid, GrapplingJoint);

args.Handled = true;
}

private void SetReeling(EntityUid uid, FishingRodComponent 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<FishingRodComponent>();

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, FishingProjectileComponent component, ref ProjectileEmbedEvent args)
{
if (!Timing.IsFirstTimePredicted)
return;

//joint between the embedded and the weapon
var jointComp = EnsureComp<JointComponent>(args.Weapon);
var joint = _joints.CreateDistanceJoint(args.Weapon, args.Embedded, 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(args.Weapon, jointComp);
}

private void OnRemoveEmbed(EntityUid uid, FishingProjectileComponent component, RemoveEmbedEvent args)
{
if (TryComp<EmbeddableProjectileComponent>(uid, out var projectile))
{
if (projectile.EmbeddedIntoUid != null)
{
_joints.ClearJoints(projectile.EmbeddedIntoUid.Value);
}
}
}

[Serializable, NetSerializable]
protected sealed class RequestGrapplingReelMessage : EntityEventArgs
{
public bool Reeling;

public RequestGrapplingReelMessage(bool reeling)
{
Reeling = reeling;
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
research-technology-advanced-sidearms = Advanced Sidearms
research-technology-basic-fishing = Fishing
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@
- DeviceQuantumSpinInverter
- CleanerGrenade #imp addition
- PlantAnalyzer #NF addition
- FishingRod #imp addition
- type: EmagLatheRecipes
emagDynamicRecipes:
- BoxBeanbag
Expand Down
Empty file.
Loading

0 comments on commit e0b4c5d

Please sign in to comment.