From b32235f958d69042611ebb3602148bccf2afd16d Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 15 Dec 2024 19:27:59 +0300 Subject: [PATCH 1/6] Configurable lengths, anchorable leashes, refactor --- .../Leash/Components/LeashComponent.cs | 6 + .../Floofstation/Leash/LeashSystem.cs | 111 +++++++++++++----- .../Locale/en-US/Floof/leash/leash-verbs.ftl | 1 + .../en-US/Floof/verbs/verb-categories.ftl | 1 + Resources/Migrations/floofmigration.yml | 3 + .../Entities/Structures/Machines/lathe.yml | 2 - .../Floof/Catalog/Fills/Crates/lewd.yml | 2 +- .../Floof/Entities/Objects/Tools/leash.yml | 29 ++--- Resources/Prototypes/Floof/Loadouts/items.yml | 12 -- .../Prototypes/Floof/Recipes/Lathes/tools.yml | 10 -- .../Floof/Interface/VerbIcons/resize.svg | 1 + .../Interface/VerbIcons/resize.svg.192dpi.png | Bin 0 -> 3277 bytes 12 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 Resources/Locale/en-US/Floof/verbs/verb-categories.ftl create mode 100644 Resources/Textures/Floof/Interface/VerbIcons/resize.svg create mode 100644 Resources/Textures/Floof/Interface/VerbIcons/resize.svg.192dpi.png diff --git a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs index 181d61b0715..2e213a36ce1 100644 --- a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs +++ b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs @@ -19,6 +19,12 @@ public sealed partial class LeashComponent : Component [DataField, AutoNetworkedField] public float Length = 3.5f; + /// + /// List of possible lengths this leash may be assigned to be the user. If null, the length cannot be changed. + /// + [DataField, AutoNetworkedField] + public float[]? LengthConfigs; + /// /// Maximum distance between the anchor and the puller beyond which the leash will break. /// diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index 9b4bd293f9a..3cff1341c7b 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -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)); @@ -47,6 +52,7 @@ public override void Initialize() SubscribeLocalEvent(OnLeashInserted); SubscribeLocalEvent(OnLeashRemoved); + SubscribeLocalEvent>(OnGetLeashVerbs); SubscribeLocalEvent(OnAttachDoAfter); SubscribeLocalEvent(OnDetachDoAfter); @@ -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(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(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 ent, ref BeingUnequippedAttemptEvent args) @@ -108,7 +127,7 @@ private void OnAnchorUnequipping(Entity ent, ref BeingUneq if (TryGetLeashTarget(args.Equipment, out var leashTarget) && TryComp(leashTarget, out var leashed) && leashed.Puller is not null - ) + ) args.Cancel(); } @@ -130,13 +149,15 @@ private void OnGetEquipmentVerbs(Entity 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(leashTarget, out var leashedComp) || leashedComp.Puller != leash - || HasComp(leashTarget)) // This one means that OnGetLeashedVerbs will add a verb to remove it + || HasComp( + leashTarget)) // This one means that OnGetLeashedVerbs will add a verb to remove it return; var unleashVerb = new EquipmentVerb @@ -163,7 +184,24 @@ private void OnGetLeashedVerbs(Entity ent, ref GetVerbsEvent ent, ref JointRemovedEvent args) + private void OnGetLeashVerbs(Entity ent, ref GetVerbsEvent 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 ent, ref JointRemovedEvent args) { var id = args.Joint.ID; if (_timing.ApplyingState @@ -301,11 +339,15 @@ public bool CanCreateJoint(EntityUid a, EntityUid b) private DistanceJoint CreateLeashJoint(string jointId, Entity 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; @@ -461,6 +503,15 @@ public void RemoveLeash(Entity leashed, Entity + /// Sets the desired length of the leash. The actual length will be updated on the next physics tick. + /// + public void SetLeashLength(Entity leash, float length) + { + leash.Comp.Length = length; + RefreshJoints(leash); + } + /// /// Refreshes all joints for the specified leash. /// This will remove all obsolete joints, such as those for which CanCreateJoint returns false, diff --git a/Resources/Locale/en-US/Floof/leash/leash-verbs.ftl b/Resources/Locale/en-US/Floof/leash/leash-verbs.ftl index 61d83139512..6329d964529 100644 --- a/Resources/Locale/en-US/Floof/leash/leash-verbs.ftl +++ b/Resources/Locale/en-US/Floof/leash/leash-verbs.ftl @@ -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 diff --git a/Resources/Locale/en-US/Floof/verbs/verb-categories.ftl b/Resources/Locale/en-US/Floof/verbs/verb-categories.ftl new file mode 100644 index 00000000000..ec591148c09 --- /dev/null +++ b/Resources/Locale/en-US/Floof/verbs/verb-categories.ftl @@ -0,0 +1 @@ +verb-categories-leash-config = Set Length diff --git a/Resources/Migrations/floofmigration.yml b/Resources/Migrations/floofmigration.yml index 5e4e0419c7c..c261f3a7670 100644 --- a/Resources/Migrations/floofmigration.yml +++ b/Resources/Migrations/floofmigration.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 3f87a32ec2b..fdd2b349dcb 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -198,7 +198,6 @@ - ClothingHeadHatWelding - ShockCollar # FloofStation - LeashBasic # FloofStation - - ShortLeash # FloofStation - type: EmagLatheRecipes emagStaticRecipes: - BoxLethalshot @@ -1232,7 +1231,6 @@ - ShockCollar - CustomDrinkJug - LeashBasic - - ShortLeash - CellRechargerCircuitboard - WeaponCapacitorRechargerCircuitboard - Beaker diff --git a/Resources/Prototypes/Floof/Catalog/Fills/Crates/lewd.yml b/Resources/Prototypes/Floof/Catalog/Fills/Crates/lewd.yml index 559626a760d..dd74e75a237 100644 --- a/Resources/Prototypes/Floof/Catalog/Fills/Crates/lewd.yml +++ b/Resources/Prototypes/Floof/Catalog/Fills/Crates/lewd.yml @@ -36,4 +36,4 @@ - id: DrinkCumBottleFull amount: 2 - id: LeashBasic - - id: ShortLeash + amount: 2 diff --git a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml index 61609d781e2..12f4ff4dbbd 100644 --- a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml +++ b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml @@ -14,6 +14,9 @@ leashSprite: sprite: Floof/Objects/Tools/leash-rope.rsi state: rope + - type: Anchorable + delay: 3 + snap: false # The first ever entity to set this field to false! - type: entity id: LeashBasic @@ -21,30 +24,19 @@ components: - type: Leash length: 3 + lengthConfigs: [1.5, 2.25, 3] maxDistance: 6 attachDelay: 4 detachDelay: 4 selfDetachDelay: 10 -# TODO this should be named LeashShort... -- type: entity - id: ShortLeash - parent: BaseLeash - name: short leash - components: - - type: Leash - length: 1.5 - maxDistance: 3 - attachDelay: 4.5 - detachDelay: 3 - selfDetachDelay: 10 - - type: entity id: LeashAdvanced parent: LeashBasic name: advanced leash components: - type: Leash + lengthConfigs: [1, 1.5, 2.25, 3] maxJoints: 3 attachDelay: 2.5 detachDelay: 2 @@ -58,9 +50,10 @@ suffix: DEBUG, DO NOT MAP components: - type: Leash - maxDistance: 100 - maxJoints: 25 - attachDelay: 0 - detachDelay: 10000 # will still be instant for admin ghosts or whatever with instant doafters tag - selfDetachDelay: 10000 + lengthConfigs: [1, 1.5, 2.25, 3, 5, 10] + maxDistance: 20 + maxJoints: 10 + attachDelay: 2.5 + detachDelay: 5 + selfDetachDelay: 20 pullInterval: 0.1 diff --git a/Resources/Prototypes/Floof/Loadouts/items.yml b/Resources/Prototypes/Floof/Loadouts/items.yml index 0cec29c6be0..fa562950689 100644 --- a/Resources/Prototypes/Floof/Loadouts/items.yml +++ b/Resources/Prototypes/Floof/Loadouts/items.yml @@ -30,15 +30,3 @@ requirements: - !type:CharacterItemGroupRequirement group: LoadoutFun - -- type: loadout - id: LoadoutItemLeashShort - category: Items - cost: 2 - exclusive: true - items: - - ShortLeash - requirements: - - !type:CharacterItemGroupRequirement - group: LoadoutFun - diff --git a/Resources/Prototypes/Floof/Recipes/Lathes/tools.yml b/Resources/Prototypes/Floof/Recipes/Lathes/tools.yml index 69d3ecba2ac..2492871df64 100644 --- a/Resources/Prototypes/Floof/Recipes/Lathes/tools.yml +++ b/Resources/Prototypes/Floof/Recipes/Lathes/tools.yml @@ -6,13 +6,3 @@ Cloth: 50 Plastic: 500 Steel: 75 - -- type: latheRecipe - id: ShortLeash - result: ShortLeash - completetime: 2.5 - materials: - Cloth: 25 - Plastic: 300 - Steel: 50 - diff --git a/Resources/Textures/Floof/Interface/VerbIcons/resize.svg b/Resources/Textures/Floof/Interface/VerbIcons/resize.svg new file mode 100644 index 00000000000..7f0b49ff7d9 --- /dev/null +++ b/Resources/Textures/Floof/Interface/VerbIcons/resize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Resources/Textures/Floof/Interface/VerbIcons/resize.svg.192dpi.png b/Resources/Textures/Floof/Interface/VerbIcons/resize.svg.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..5947c72832579fc331b69bd7471021546e63b9dc GIT binary patch literal 3277 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 W3qN$>U&`cT3fSip;9b8bg7IJf&?5-| literal 0 HcmV?d00001 From 80971ed9b4e81afa779baa5ba881d415b8f510af Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 15 Dec 2024 20:48:03 +0300 Subject: [PATCH 2/6] Reduce the visual size of the leash --- Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml index 12f4ff4dbbd..5706b7fe7be 100644 --- a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml +++ b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml @@ -9,6 +9,7 @@ sprite: Floof/Objects/Tools/leash.rsi layers: - state: icon + scale: 0.5, 0.5 - type: Leash pullInterval: 0.75 leashSprite: From 09069220e26fa858b51c0eb06dfdef4b5c4ddb34 Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 15 Dec 2024 21:49:28 +0300 Subject: [PATCH 3/6] Configurable offsets & a custom visualizer --- .../Floofstation/Leash/LeashVisualsOverlay.cs | 89 +++++++++++++++++++ .../Floofstation/Leash/LeashVisualsSystem.cs | 21 +++++ .../Leash/Components/LeashAnchorComponent.cs | 7 ++ .../Components/LeashedVisualsComponent.cs | 21 +++++ .../Floofstation/Leash/LeashSystem.cs | 6 +- .../Entities/Clothing/Neck/misc.yml | 3 +- .../Floof/Entities/Clothing/Neck/collars.yml | 1 + .../Floof/Entities/Mobs/NPCs/scugcat.yml | 1 + .../Floof/Entities/Objects/Tools/leash.yml | 4 +- .../Entities/Objects/Devices/shock_collar.yml | 3 +- 10 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs create mode 100644 Content.Client/Floofstation/Leash/LeashVisualsSystem.cs create mode 100644 Content.Shared/Floofstation/Leash/Components/LeashedVisualsComponent.cs diff --git a/Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs b/Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs new file mode 100644 index 00000000000..41319ea29fb --- /dev/null +++ b/Content.Client/Floofstation/Leash/LeashVisualsOverlay.cs @@ -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 _xformQuery; + private readonly EntityQuery _spriteQuery; + + public LeashVisualsOverlay(IEntityManager entMan) + { + _entMan = entMan; + _sprites = _entMan.System(); + _xform = _entMan.System(); + _xformQuery = _entMan.GetEntityQuery(); + _spriteQuery = _entMan.GetEntityQuery(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + var worldHandle = args.WorldHandle; + worldHandle.SetTransform(Vector2.Zero, Angle.Zero); + + var query = _entMan.EntityQueryEnumerator(); + 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); + } + } +} diff --git a/Content.Client/Floofstation/Leash/LeashVisualsSystem.cs b/Content.Client/Floofstation/Leash/LeashVisualsSystem.cs new file mode 100644 index 00000000000..05d0f6085e2 --- /dev/null +++ b/Content.Client/Floofstation/Leash/LeashVisualsSystem.cs @@ -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(); + } +} diff --git a/Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs index a9fdb555937..465a96eb6e2 100644 --- a/Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs +++ b/Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs @@ -1,3 +1,5 @@ +using System.Numerics; + namespace Content.Shared.Floofstation.Leash.Components; /// @@ -6,4 +8,9 @@ namespace Content.Shared.Floofstation.Leash.Components; [RegisterComponent] public sealed partial class LeashAnchorComponent : Component { + /// + /// The visual offset of the "anchor point". + /// + [DataField] + public Vector2 Offset = Vector2.Zero; } diff --git a/Content.Shared/Floofstation/Leash/Components/LeashedVisualsComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashedVisualsComponent.cs new file mode 100644 index 00000000000..2aeae642967 --- /dev/null +++ b/Content.Shared/Floofstation/Leash/Components/LeashedVisualsComponent.cs @@ -0,0 +1,21 @@ +using System.Numerics; +using Robust.Shared.GameStates; +using Robust.Shared.Utility; + +namespace Content.Shared.Floofstation.Leash.Components; + +/// +/// Draws a line between this entity and the target. Same as JointVisualsComponent. +/// +[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; +} diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index 3cff1341c7b..fa865d839b3 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -467,9 +467,11 @@ public void DoLeash(Entity anchor, Entity _container.EnsureContainer(leashTarget, LeashedComponent.VisualsContainerName); if (EntityManager.TrySpawnInContainer(null, leashTarget, LeashedComponent.VisualsContainerName, out var visualEntity)) { - var visualComp = EnsureComp(visualEntity.Value); + var visualComp = EnsureComp(visualEntity.Value); visualComp.Sprite = sprite; - visualComp.Target = leash; + visualComp.Source = leash; + visualComp.Target = leashTarget; + visualComp.OffsetTarget = anchor.Comp.Offset; data.LeashVisuals = GetNetEntity(visualEntity); } diff --git a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml index 2581f06bb8f..03b17264276 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/misc.yml @@ -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! @@ -93,4 +93,3 @@ - type: EmitsSoundOnMove soundCollection: collection: FootstepJester - - type: LeashAnchor # Floofstation, silly bell collar can be leashed :3 diff --git a/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml index 8696ee9ef9e..73041304522 100644 --- a/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml +++ b/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml @@ -5,6 +5,7 @@ components: - type: Clothing - type: LeashAnchor + offset: 0, 0.3 - type: entity parent: ClothingNeckCollarBase diff --git a/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml b/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml index af3ee8c25e1..d1556f300ef 100644 --- a/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml +++ b/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml @@ -105,6 +105,7 @@ - ScugSign - Cat - type: LeashAnchor # Floofstation + offset: 0, 0.2 - type: Vore - type: palette diff --git a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml index 5706b7fe7be..e535373ade5 100644 --- a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml +++ b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml @@ -9,7 +9,7 @@ sprite: Floof/Objects/Tools/leash.rsi layers: - state: icon - scale: 0.5, 0.5 + scale: 0.7, 0.7 - type: Leash pullInterval: 0.75 leashSprite: @@ -29,7 +29,7 @@ maxDistance: 6 attachDelay: 4 detachDelay: 4 - selfDetachDelay: 10 + selfDetachDelay: 10ug - type: entity id: LeashAdvanced diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml index bfec2afcf06..d5979aa77c2 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase # Floof - reparented id: ShockCollar name: shock collar description: Shocking. # DeltaV: sprite is fine @@ -22,4 +22,3 @@ - type: DeviceLinkSink ports: - Trigger - - type: LeashAnchor # floofstation From bec2267967b46fded8f480916927eac3cc6a0d4b Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 15 Dec 2024 21:52:50 +0300 Subject: [PATCH 4/6] Wrong indentation --- Content.Shared/Floofstation/Leash/LeashSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index fa865d839b3..94f9110fe02 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -201,7 +201,7 @@ private void OnGetLeashVerbs(Entity ent, ref GetVerbsEvent ent, ref JointRemovedEvent args) + private void OnJointRemoved(Entity ent, ref JointRemovedEvent args) { var id = args.Joint.ID; if (_timing.ApplyingState From a68312c8468871345fc11a98710a091f50f2a99c Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 15 Dec 2024 22:10:02 +0300 Subject: [PATCH 5/6] Eugh how did I make this mistake? --- Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml | 2 +- Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml index 73041304522..9215337badb 100644 --- a/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml +++ b/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml @@ -5,7 +5,7 @@ components: - type: Clothing - type: LeashAnchor - offset: 0, 0.3 + offset: 0, 0.22 - type: entity parent: ClothingNeckCollarBase diff --git a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml index e535373ade5..67ceacf187a 100644 --- a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml +++ b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml @@ -29,7 +29,7 @@ maxDistance: 6 attachDelay: 4 detachDelay: 4 - selfDetachDelay: 10ug + selfDetachDelay: 10 - type: entity id: LeashAdvanced From 874d5a81fe1f49b1142a47619324b029e90bc8ad Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 15 Dec 2024 22:10:19 +0300 Subject: [PATCH 6/6] Fix the remove leash verb not appearing on clothing --- Content.Shared/Floofstation/Leash/LeashSystem.cs | 3 +-- Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index 94f9110fe02..411fee0116d 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -156,8 +156,7 @@ private void OnGetEquipmentVerbs(Entity ent, ref GetVerbsE if (!TryGetLeashTarget(ent!, out var leashTarget) || !TryComp(leashTarget, out var leashedComp) || leashedComp.Puller != leash - || HasComp( - leashTarget)) // This one means that OnGetLeashedVerbs will add a verb to remove it + || HasComp(ent)) // This one means that OnGetLeashedVerbs will add a verb to remove it return; var unleashVerb = new EquipmentVerb diff --git a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml index 67ceacf187a..072b8545c94 100644 --- a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml +++ b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml @@ -17,7 +17,7 @@ state: rope - type: Anchorable delay: 3 - snap: false # The first ever entity to set this field to false! + snap: false # The first ever entity to set this field to false! However, this doesn't seem to work :c - type: entity id: LeashBasic