Skip to content

Commit

Permalink
Merge pull request #203 from Mnemotechnician/fix/another-leash-fix
Browse files Browse the repository at this point in the history
Another Leash Fix
  • Loading branch information
Memeji authored Sep 23, 2024
2 parents b0befae + 30fc5f2 commit d7818c9
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ public sealed partial class LeashComponent : Component
[DataDefinition, Serializable, NetSerializable]
public sealed partial class LeashData
{
/// <summary>
/// Id of the joint created by this leash. May be null if this leash does not currently create a joint
/// (e.g. because it's attached to the same entity who holds it)
/// </summary>
[DataField]
public string JointId = string.Empty;
public string? JointId = null;

[DataField]
public NetEntity Pulled = NetEntity.Invalid;
Expand All @@ -67,7 +71,7 @@ public sealed partial class LeashData
[DataField]
public NetEntity? LeashVisuals = null;

public LeashData(string jointId, NetEntity pulled)
public LeashData(string? jointId, NetEntity pulled)
{
JointId = jointId;
Pulled = pulled;
Expand Down
111 changes: 97 additions & 14 deletions Content.Shared/Floofstation/Leash/LeashSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public override void Initialize()
SubscribeLocalEvent<LeashedComponent, JointRemovedEvent>(OnJointRemoved, after: [typeof(SharedJointSystem)]);
SubscribeLocalEvent<LeashedComponent, GetVerbsEvent<InteractionVerb>>(OnGetLeashedVerbs);

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

SubscribeLocalEvent<LeashAnchorComponent, LeashAttachDoAfterEvent>(OnAttachDoAfter);
SubscribeLocalEvent<LeashedComponent, LeashDetachDoAfterEvent>(OnDetachDoAfter);

Expand Down Expand Up @@ -74,6 +77,7 @@ public override void Update(float frameTime)

// 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
Expand Down Expand Up @@ -167,17 +171,37 @@ private void OnJointRemoved(Entity<LeashedComponent> ent, ref JointRemovedEvent
|| ent.Comp.Puller is not { } puller
|| !TryComp<LeashAnchorComponent>(ent.Comp.Anchor, out var anchor)
|| !TryComp<LeashComponent>(puller, out var leash)
|| leash.Leashed.All(it => it.JointId != id)
|| !Transform(ent).Coordinates.TryDistance(EntityManager, Transform(puller).Coordinates, out var dst)
|| dst > leash.MaxDistance
)
|| leash.Leashed.All(it => it.JointId != id))
return;

// If the entity still has a leashed comp, and is on the same map, and is within the max distance of the leash
// Then the leash was likely broken due to some weird unforeseen fucking robust toolbox magic. We can try to recreate it.
// This is hella unsafe to do. It will crash in debug builds under certain conditions. Luckily, release builds are safe.
RemoveLeash(ent!, (puller, leash), false);
DoLeash((ent.Comp.Anchor.Value, anchor), (puller, leash), ent);

// If the entity still has a leashed comp, and is on the same map, and is within the max distance of the leash
// Then the leash was likely broken due to some weird unforeseen fucking robust toolbox magic.
// We can try to recreate it, but on the next tick.
Timer.Spawn(0, () =>
{
if (TerminatingOrDeleted(ent.Comp.Anchor.Value)
|| TerminatingOrDeleted(puller)
|| !Transform(ent).Coordinates.TryDistance(EntityManager, Transform(puller).Coordinates, out var dst)
|| dst > leash.MaxDistance
)
return;
DoLeash((ent.Comp.Anchor.Value, anchor), (puller, leash), ent);
});
}

private void OnLeashInserted(Entity<LeashComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
if (!_net.IsClient)
RefreshJoints(ent);
}

private void OnLeashRemoved(Entity<LeashComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
if (!_net.IsClient)
RefreshJoints(ent);
}

private void OnAttachDoAfter(Entity<LeashAnchorComponent> ent, ref LeashAttachDoAfterEvent args)
Expand Down Expand Up @@ -257,9 +281,27 @@ private bool TryGetLeashTarget(Entity<LeashAnchorComponent?> ent, out EntityUid
return true;
}

/// <summary>
/// Returns true if a leash joint can be created between the two specified entities.
/// This will return false if one of the entities is a parent of another.
/// </summary>
public bool CanCreateJoint(EntityUid a, EntityUid b)
{
BaseContainer? aOuter = null, bOuter = null;

// If neither of the entities are in contianers, it's safe to create a joint
if (!_container.TryGetOuterContainer(a, Transform(a), out aOuter)
&& !_container.TryGetOuterContainer(b, Transform(b), out bOuter))
return true;

// Otherwise, we need to make sure that neither of the entities contain the other, and that they are not in the same container.
return a != bOuter?.Owner && b != aOuter?.Owner && aOuter?.Owner != bOuter?.Owner;
}

private DistanceJoint CreateLeashJoint(string jointId, Entity<LeashComponent> leash, EntityUid leashTarget)
{
var joint = _joints.CreateDistanceJoint(leash, leashTarget, id: jointId);

joint.CollideConnected = false;
joint.Length = leash.Comp.Length;
joint.MinLength = 0f;
Expand All @@ -281,10 +323,10 @@ public bool CanLeash(Entity<LeashAnchorComponent> anchor, Entity<LeashComponent>
&& TryGetLeashTarget(anchor!, out var leashTarget)
&& CompOrNull<LeashedComponent>(leashTarget)?.JointId == null
&& Transform(anchor).Coordinates.TryDistance(EntityManager, Transform(leash).Coordinates, out var dst)
&& dst <= leash.Comp.Length
&& !_xform.IsParentOf(Transform(leashTarget), leash); // google recursion - this makes the game explode for some reason
&& dst <= leash.Comp.Length;
}


public bool TryLeash(Entity<LeashAnchorComponent> anchor, Entity<LeashComponent> leash, EntityUid user, bool popup = true)
{
if (!CanLeash(anchor, leash) || !TryGetLeashTarget(anchor!, out var leashTarget))
Expand Down Expand Up @@ -362,13 +404,21 @@ public void DoLeash(Entity<LeashAnchorComponent> anchor, Entity<LeashComponent>

var leashedComp = EnsureComp<LeashedComponent>(leashTarget);
var netLeashTarget = GetNetEntity(leashTarget);
leashedComp.JointId = $"leash-joint-{netLeashTarget}";
var data = new LeashComponent.LeashData(null, netLeashTarget);

leashedComp.Puller = leash;
leashedComp.Anchor = anchor;

// I'd like to use a chain joint or smth, but it's too hard and oftentimes buggy - lamia is a good bad example of that.
var joint = CreateLeashJoint(leashedComp.JointId, leash, leashTarget);
var data = new LeashComponent.LeashData(leashedComp.JointId, netLeashTarget);
if (CanCreateJoint(leashTarget, leash))
{
var jointId = $"leash-joint-{netLeashTarget}";
var joint = CreateLeashJoint(jointId, leash, leashTarget);
data.JointId = leashedComp.JointId = jointId;
}
else
{
leashedComp.JointId = null;
}

if (leash.Comp.LeashSprite is { } sprite)
{
Expand Down Expand Up @@ -411,5 +461,38 @@ public void RemoveLeash(Entity<LeashedComponent?> leashed, Entity<LeashComponent
Dirty(leash);
}

/// <summary>
/// Refreshes all joints for the specified leash.
/// This will remove all obsolete joints, such as those for which CanCreateJoint returns false,
/// and re-add all joints that were previously removed for the same reason, but became valid later.
/// </summary>
public void RefreshJoints(Entity<LeashComponent> leash)
{
foreach (var data in leash.Comp.Leashed)
{
if (!TryGetEntity(data.Pulled, out var pulled) || !TryComp<LeashedComponent>(pulled, out var leashed))
continue;

var shouldExist = CanCreateJoint(pulled.Value, leash);
var exists = data.JointId != null;

if (exists && !shouldExist && TryComp<JointComponent>(pulled, out var jointComp) && jointComp.GetJoints.TryGetValue(data.JointId!, out var joint))
{
data.JointId = leashed.JointId = null;
_joints.RemoveJoint(joint);

Log.Debug($"Removed obsolete leash joint between {leash.Owner} and {pulled.Value}");
}
else if (!exists && shouldExist)
{
var jointId = $"leash-joint-{data.Pulled}";
joint = CreateLeashJoint(jointId, leash, pulled.Value);
data.JointId = leashed.JointId = jointId;

Log.Debug($"Added new leash joint between {leash.Owner} and {pulled.Value}");
}
}
}

#endregion
}

0 comments on commit d7818c9

Please sign in to comment.