Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More Leash Tweaks #414

Merged
merged 6 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Numerics;
using Content.Shared.Floofstation.Leash.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;

namespace Content.Client.Floofstation.Leash;

public sealed class LeashVisualsOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;

private readonly IEntityManager _entMan;
private readonly SpriteSystem _sprites;
private readonly SharedTransformSystem _xform;
private readonly EntityQuery<TransformComponent> _xformQuery;
private readonly EntityQuery<SpriteComponent> _spriteQuery;

public LeashVisualsOverlay(IEntityManager entMan)
{
_entMan = entMan;
_sprites = _entMan.System<SpriteSystem>();
_xform = _entMan.System<SharedTransformSystem>();
_xformQuery = _entMan.GetEntityQuery<TransformComponent>();
_spriteQuery = _entMan.GetEntityQuery<SpriteComponent>();
}

protected override void Draw(in OverlayDrawArgs args)
{
var worldHandle = args.WorldHandle;
worldHandle.SetTransform(Vector2.Zero, Angle.Zero);

var query = _entMan.EntityQueryEnumerator<LeashedVisualsComponent>();
while (query.MoveNext(out var visualsComp))
{
if (visualsComp.Source is not {Valid: true} source
|| visualsComp.Target is not {Valid: true} target
|| !_xformQuery.TryGetComponent(source, out var xformComp)
|| !_xformQuery.TryGetComponent(target, out var otherXformComp)
|| xformComp.MapID != args.MapId
|| otherXformComp.MapID != xformComp.MapID)
continue;

var texture = _sprites.Frame0(visualsComp.Sprite);
var width = texture.Width / (float) EyeManager.PixelsPerMeter;

var coordsA = xformComp.Coordinates;
var coordsB = otherXformComp.Coordinates;

// If both coordinates are in the same spot (e.g. the leash is being held by the leashed), don't render anything
if (coordsA.TryDistance(_entMan, _xform, coordsB, out var dist) && dist < 0.01f)
continue;

var rotA = xformComp.LocalRotation;
var rotB = otherXformComp.LocalRotation;
var offsetA = visualsComp.OffsetSource;
var offsetB = visualsComp.OffsetTarget;

// Sprite rotation is the rotation along the Z axis
// Which is different from transform rotation for all mobs that are seen from the side (instead of top-down)
if (_spriteQuery.TryGetComponent(source, out var spriteA))
{
offsetA *= spriteA.Scale;
rotA = spriteA.Rotation;
}
if (_spriteQuery.TryGetComponent(target, out var spriteB))
{
offsetB *= spriteB.Scale;
rotB = spriteB.Rotation;
}

coordsA = coordsA.Offset(rotA.RotateVec(offsetA));
coordsB = coordsB.Offset(rotB.RotateVec(offsetB));

var posA = _xform.ToMapCoordinates(coordsA).Position;
var posB = _xform.ToMapCoordinates(coordsB).Position;
var diff = (posB - posA);
var length = diff.Length();

// So basically, we find the midpoint, then create a box that describes the sprite boundaries, then rotate it
var midPoint = diff / 2f + posA;
var angle = (posB - posA).ToWorldAngle();
var box = new Box2(-width / 2f, -length / 2f, width / 2f, length / 2f);
var rotate = new Box2Rotated(box.Translated(midPoint), angle, midPoint);

worldHandle.DrawTextureRect(texture, rotate);
}
}
}
21 changes: 21 additions & 0 deletions Content.Client/Floofstation/Leash/LeashVisualsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Content.Client.Physics;
using Robust.Client.Graphics;

namespace Content.Client.Floofstation.Leash;

public sealed class LeashVisualsSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlay = default!;

public override void Initialize()
{
base.Initialize();
_overlay.AddOverlay(new LeashVisualsOverlay(EntityManager));
}

public override void Shutdown()
{
base.Shutdown();
_overlay.RemoveOverlay<LeashVisualsOverlay>();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Numerics;

namespace Content.Shared.Floofstation.Leash.Components;

/// <summary>
Expand All @@ -6,4 +8,9 @@ namespace Content.Shared.Floofstation.Leash.Components;
[RegisterComponent]
public sealed partial class LeashAnchorComponent : Component
{
/// <summary>
/// The visual offset of the "anchor point".
/// </summary>
[DataField]
public Vector2 Offset = Vector2.Zero;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public sealed partial class LeashComponent : Component
[DataField, AutoNetworkedField]
public float Length = 3.5f;

/// <summary>
/// List of possible lengths this leash may be assigned to be the user. If null, the length cannot be changed.
/// </summary>
[DataField, AutoNetworkedField]
public float[]? LengthConfigs;

/// <summary>
/// Maximum distance between the anchor and the puller beyond which the leash will break.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Numerics;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;

namespace Content.Shared.Floofstation.Leash.Components;

/// <summary>
/// Draws a line between this entity and the target. Same as JointVisualsComponent.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class LeashedVisualsComponent : Component
{
[DataField(required: true), AutoNetworkedField]
public SpriteSpecifier Sprite = default!;

[DataField, AutoNetworkedField]
public EntityUid Source, Target;

[DataField, AutoNetworkedField]
public Vector2 OffsetSource, OffsetTarget;
}
114 changes: 83 additions & 31 deletions Content.Shared/Floofstation/Leash/LeashSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public sealed class LeashSystem : EntitySystem
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;

public static VerbCategory LeashLengthConfigurationCategory =
new("verb-categories-leash-config", "/Textures/Floof/Interface/VerbIcons/resize.svg.192dpi.png");

#region Lifecycle

public override void Initialize()
{
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
Expand All @@ -47,6 +52,7 @@ public override void Initialize()

SubscribeLocalEvent<LeashComponent, EntGotInsertedIntoContainerMessage>(OnLeashInserted);
SubscribeLocalEvent<LeashComponent, EntGotRemovedFromContainerMessage>(OnLeashRemoved);
SubscribeLocalEvent<LeashComponent, GetVerbsEvent<AlternativeVerb>>(OnGetLeashVerbs);

SubscribeLocalEvent<LeashAnchorComponent, LeashAttachDoAfterEvent>(OnAttachDoAfter);
SubscribeLocalEvent<LeashedComponent, LeashDetachDoAfterEvent>(OnDetachDoAfter);
Expand All @@ -69,37 +75,50 @@ public override void Update(float frameTime)
while (leashQuery.MoveNext(out var leashEnt, out var leash, out var physics))
{
var sourceXForm = Transform(leashEnt);

foreach (var data in leash.Leashed.ToList())
{
if (data.Pulled == NetEntity.Invalid || !TryGetEntity(data.Pulled, out var target))
continue;

// Client side only: set max distance to infinity to prevent the client from ever predicting leashes.
if (_net.IsClient
&& data.JointId is not null
&& TryComp<JointComponent>(target, out var jointComp)
&& jointComp.GetJoints.TryGetValue(data.JointId, out var joint)
&& joint is DistanceJoint distanceJoint
)
distanceJoint.MaxLength = float.MaxValue;

if (_net.IsClient)
continue;

// Break each leash joint whose entities are on different maps or are too far apart
var targetXForm = Transform(target.Value);
if (targetXForm.MapUid != sourceXForm.MapUid
|| !sourceXForm.Coordinates.TryDistance(EntityManager, targetXForm.Coordinates, out var dst)
|| dst > leash.MaxDistance
)
RemoveLeash(target.Value, (leashEnt, leash));
}
UpdateLeash(data, sourceXForm, leash, leashEnt);
}

leashQuery.Dispose();
}

private void UpdateLeash(LeashComponent.LeashData data, TransformComponent sourceXForm, LeashComponent leash, EntityUid leashEnt)
{
if (data.Pulled == NetEntity.Invalid || !TryGetEntity(data.Pulled, out var target))
return;

DistanceJoint? joint = null;
if (data.JointId is not null
&& TryComp<JointComponent>(target, out var jointComp)
&& jointComp.GetJoints.TryGetValue(data.JointId, out var _joint)
)
joint = _joint as DistanceJoint;

// Client: set max distance to infinity to prevent the client from ever predicting leashes.
if (_net.IsClient)
{
if (joint is not null)
joint.MaxLength = float.MaxValue;

return;
}

// Server: break each leash joint whose entities are on different maps or are too far apart
var targetXForm = Transform(target.Value);
if (targetXForm.MapUid != sourceXForm.MapUid
|| !sourceXForm.Coordinates.TryDistance(EntityManager, targetXForm.Coordinates, out var dst)
|| dst > leash.MaxDistance
)
RemoveLeash(target.Value, (leashEnt, leash));

// Server: update leash lengths if necessary/possible
// The length can be increased freely, but can only be decreased if the pulled entity is close enough
if (joint is not null && (leash.Length >= joint.MaxLength || leash.Length >= joint.Length))
joint.MaxLength = leash.Length;
}

#endregion

#region event handling

private void OnAnchorUnequipping(Entity<LeashAnchorComponent> ent, ref BeingUnequippedAttemptEvent args)
Expand All @@ -108,7 +127,7 @@ private void OnAnchorUnequipping(Entity<LeashAnchorComponent> ent, ref BeingUneq
if (TryGetLeashTarget(args.Equipment, out var leashTarget)
&& TryComp<LeashedComponent>(leashTarget, out var leashed)
&& leashed.Puller is not null
)
)
args.Cancel();
}

Expand All @@ -130,13 +149,14 @@ private void OnGetEquipmentVerbs(Entity<LeashAnchorComponent> ent, ref GetVerbsE
leashVerb.Message = Loc.GetString("verb-leash-error-message");
leashVerb.Disabled = true;
}

args.Verbs.Add(leashVerb);


if (!TryGetLeashTarget(ent!, out var leashTarget)
|| !TryComp<LeashedComponent>(leashTarget, out var leashedComp)
|| leashedComp.Puller != leash
|| HasComp<LeashedComponent>(leashTarget)) // This one means that OnGetLeashedVerbs will add a verb to remove it
|| HasComp<LeashedComponent>(ent)) // This one means that OnGetLeashedVerbs will add a verb to remove it
return;

var unleashVerb = new EquipmentVerb
Expand All @@ -163,6 +183,23 @@ private void OnGetLeashedVerbs(Entity<LeashedComponent> ent, ref GetVerbsEvent<I
});
}

private void OnGetLeashVerbs(Entity<LeashComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (ent.Comp.LengthConfigs is not { } configurations)
return;

// Add a menu listing each length configuration
foreach (var length in configurations)
{
args.Verbs.Add(new AlternativeVerb
{
Text = Loc.GetString("verb-leash-set-length-text", ("length", length)),
Act = () => SetLeashLength(ent, length),
Category = LeashLengthConfigurationCategory
});
}
}

private void OnJointRemoved(Entity<LeashedComponent> ent, ref JointRemovedEvent args)
{
var id = args.Joint.ID;
Expand Down Expand Up @@ -301,11 +338,15 @@ public bool CanCreateJoint(EntityUid a, EntityUid b)
private DistanceJoint CreateLeashJoint(string jointId, Entity<LeashComponent> leash, EntityUid leashTarget)
{
var joint = _joints.CreateDistanceJoint(leash, leashTarget, id: jointId);
// If the soon-to-be-leashed entity is too far away, we don't force it any closer.
// The system will automatically reduce the length of the leash once it gets closer.
var length = Transform(leashTarget).Coordinates.TryDistance(EntityManager, Transform(leash).Coordinates, out var dist)
? MathF.Max(dist, leash.Comp.Length)
: leash.Comp.Length;

joint.CollideConnected = false;
joint.Length = leash.Comp.Length;
joint.MinLength = 0f;
joint.MaxLength = leash.Comp.Length;
joint.MaxLength = length;
joint.Stiffness = 1f;
joint.CollideConnected = true; // This is just for performance reasons and doesn't actually make mobs collide.
joint.Damping = 1f;
Expand Down Expand Up @@ -425,9 +466,11 @@ public void DoLeash(Entity<LeashAnchorComponent> anchor, Entity<LeashComponent>
_container.EnsureContainer<ContainerSlot>(leashTarget, LeashedComponent.VisualsContainerName);
if (EntityManager.TrySpawnInContainer(null, leashTarget, LeashedComponent.VisualsContainerName, out var visualEntity))
{
var visualComp = EnsureComp<JointVisualsComponent>(visualEntity.Value);
var visualComp = EnsureComp<LeashedVisualsComponent>(visualEntity.Value);
visualComp.Sprite = sprite;
visualComp.Target = leash;
visualComp.Source = leash;
visualComp.Target = leashTarget;
visualComp.OffsetTarget = anchor.Comp.Offset;

data.LeashVisuals = GetNetEntity(visualEntity);
}
Expand Down Expand Up @@ -461,6 +504,15 @@ public void RemoveLeash(Entity<LeashedComponent?> leashed, Entity<LeashComponent
Dirty(leash);
}

/// <summary>
/// Sets the desired length of the leash. The actual length will be updated on the next physics tick.
/// </summary>
public void SetLeashLength(Entity<LeashComponent> leash, float length)
{
leash.Comp.Length = length;
RefreshJoints(leash);
}

/// <summary>
/// Refreshes all joints for the specified leash.
/// This will remove all obsolete joints, such as those for which CanCreateJoint returns false,
Expand Down
1 change: 1 addition & 0 deletions Resources/Locale/en-US/Floof/leash/leash-verbs.ftl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
verb-leash-text = Attach leash
verb-leash-error-message = Cannot attach the leash to this anchor.
verb-unleash-text = Detach leash
verb-leash-set-length-text = {$length} meters
1 change: 1 addition & 0 deletions Resources/Locale/en-US/Floof/verbs/verb-categories.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
verb-categories-leash-config = Set Length
3 changes: 3 additions & 0 deletions Resources/Migrations/floofmigration.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 2024-08-16 Floof Only Please message @FracturedSwords on discord if there are any merge conflicts upcoming
SpawnMobArcticFoxSiobhan: SpawnMobArcticFoxSeb
MobArcticFoxSiobhan: MobArcticFoxSeb

# 2024-12-15: Leash now supports changing the length intrinsically
ShortLeash: LeashBasic
3 changes: 1 addition & 2 deletions Resources/Prototypes/Entities/Clothing/Neck/misc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
priority: -1

- type: entity
parent: ClothingNeckBase
parent: ClothingNeckCollarBase # Floof - reparented
id: ClothingNeckBellCollar
name: bell collar
description: A way to inform others about your presence, or just to annoy everyone around you!
Expand All @@ -93,4 +93,3 @@
- type: EmitsSoundOnMove
soundCollection:
collection: FootstepJester
- type: LeashAnchor # Floofstation, silly bell collar can be leashed :3
2 changes: 0 additions & 2 deletions Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@
- ClothingHeadHatWelding
- ShockCollar # FloofStation
- LeashBasic # FloofStation
- ShortLeash # FloofStation
- type: EmagLatheRecipes
emagStaticRecipes:
- BoxLethalshot
Expand Down Expand Up @@ -1232,7 +1231,6 @@
- ShockCollar
- CustomDrinkJug
- LeashBasic
- ShortLeash
- CellRechargerCircuitboard
- WeaponCapacitorRechargerCircuitboard
- Beaker
Expand Down
Loading
Loading