From ecef46c60b8007c26e095ae4e2934cc44f951681 Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 18 Aug 2024 19:41:28 +0300 Subject: [PATCH 01/18] Basic stuff done --- .../Leash/Components/LeashAnchorComponent.cs | 9 + .../Leash/Components/LeashComponent.cs | 95 ++++++ .../Leash/Components/LeashedComponent.cs | 11 + .../Leash/Events/LeashAttachDoAfterEvent.cs | 7 + .../Leash/Events/LeashDetachDoAfterEvent.cs | 7 + .../Floofstation/Leash/LeashSystem.cs | 315 ++++++++++++++++++ .../en-US/floofstation/leash/leash-verbs.ftl | 3 + .../Locale/en-US/floofstation/leash/leash.ftl | 8 + 8 files changed, 455 insertions(+) create mode 100644 Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs create mode 100644 Content.Shared/Floofstation/Leash/Components/LeashComponent.cs create mode 100644 Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs create mode 100644 Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs create mode 100644 Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs create mode 100644 Content.Shared/Floofstation/Leash/LeashSystem.cs create mode 100644 Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl create mode 100644 Resources/Locale/en-US/floofstation/leash/leash.ftl diff --git a/Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs new file mode 100644 index 00000000000..a9fdb555937 --- /dev/null +++ b/Content.Shared/Floofstation/Leash/Components/LeashAnchorComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Floofstation.Leash.Components; + +/// +/// Indicates that this entity or the entity that wears this entity can be leashed. +/// +[RegisterComponent] +public sealed partial class LeashAnchorComponent : Component +{ +} diff --git a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs new file mode 100644 index 00000000000..59714dfd941 --- /dev/null +++ b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs @@ -0,0 +1,95 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Floofstation.Leash.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class LeashComponent : Component +{ + /// + /// Maximum number of leash joints that this entity can create. + /// + [DataField, AutoNetworkedField] + public int MaxJoints = 1; + + /// + /// Default length of the leash joint. + /// + [DataField, AutoNetworkedField] + public float Length = 3.5f; + + /// + /// Maximum distance between the anchor and the puller beyond which the leash will break. + /// + [DataField, AutoNetworkedField] + public float MaxDistance = 8f; + + /// + /// The time it takes for one entity to attach the leash to another entity. + /// + [DataField, AutoNetworkedField] + public TimeSpan AttachDelay = TimeSpan.FromSeconds(2f), DetachDelay = TimeSpan.FromSeconds(2f); + + /// + /// The time it takes for the leashed entity to detach itself. + /// + [DataField, AutoNetworkedField] + public TimeSpan SelfDetachDelay = TimeSpan.FromSeconds(8f); + + [DataField, AutoNetworkedField] + public SpriteSpecifier? LeashSprite; + + /// + /// How much damage each leash joint can sustain before it breaks. + /// + /// Damage here actually refers to impulse exerted by the joint minus repair. + [DataField, AutoNetworkedField] + public float BreakDamage = 20f; + + /// + /// How much damage each leash joint loses every . + /// + [DataField, AutoNetworkedField] + public float JointRepairDamage = 1f; + + /// + /// Interval at which damage is calculated for each joint. + /// + [DataField, AutoNetworkedField] + public TimeSpan DamageInterval = TimeSpan.FromMilliseconds(200); + + /// + /// List of all joints and their respective pulled entities created by this leash. + /// + [DataField, AutoNetworkedField] + public List Leashed = new(); + + [DataDefinition, Serializable, NetSerializable] + public partial class LeashData + { + [DataField] + public string JointId = string.Empty; + + [DataField] + public NetEntity Pulled = NetEntity.Invalid; + + /// + /// Entity used to visualize the leash. Created dynamically. + /// + [DataField] + public NetEntity? LeashVisuals = null; + + [DataField] + public float Damage = 0f; + + [DataField] + public TimeSpan NextDamage = TimeSpan.Zero; + + public LeashData(string jointId, NetEntity pulled) + { + JointId = jointId; + Pulled = pulled; + } + }; +} diff --git a/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs new file mode 100644 index 00000000000..f0b249286b4 --- /dev/null +++ b/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Floofstation.Leash.Components; + +[RegisterComponent] +public sealed partial class LeashedComponent : Component +{ + [DataField] + public string? JointId = null; + + [NonSerialized] + public EntityUid? Puller = null; +} diff --git a/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs b/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs new file mode 100644 index 00000000000..10406a003af --- /dev/null +++ b/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; + +namespace Content.Shared.Floofstation.Leash.Events; + +public sealed class LeashAttachDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs b/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs new file mode 100644 index 00000000000..8a62dbebbfa --- /dev/null +++ b/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; + +namespace Content.Shared.Floofstation.Leash.Events; + +public sealed class LeashDetachDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs new file mode 100644 index 00000000000..35392bc0cd3 --- /dev/null +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -0,0 +1,315 @@ +using System.Linq; +using Content.Shared.Clothing.Components; +using Content.Shared.DoAfter; +using Content.Shared.Floofstation.Leash.Components; +using Content.Shared.Floofstation.Leash.Events; +using Content.Shared.Inventory; +using Content.Shared.Inventory.Events; +using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Physics; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Containers; +using Robust.Shared.Network; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared.Floofstation.Leash; + +public sealed class LeashSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfters = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedJointSystem _joints = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + + public override void Initialize() + { + UpdatesBefore.Add(typeof(SharedPhysicsSystem)); + + SubscribeLocalEvent(OnAnchorUnequipping); + SubscribeLocalEvent(OnJointRemoved); + SubscribeLocalEvent>(OnGetEquipmentVerbs); + SubscribeLocalEvent>(OnGetLeashedVerbs); + + SubscribeLocalEvent(OnAttachDoAfter); + SubscribeLocalEvent(OnDetachDoAfter); + } + + public override void Update(float frameTime) + { + var leashQuery = EntityQueryEnumerator(); + + while (leashQuery.MoveNext(out var leashEnt, out var leash)) + { + var sourceXForm = Transform(leashEnt); + + foreach (var data in leash.Leashed) + { + // Break each leash joint whose entities are on different maps or are too far apart + var target = GetEntity(data.Pulled); + var targetXForm = Transform(target); + + if (targetXForm.MapUid != sourceXForm.MapUid + || !sourceXForm.Coordinates.TryDistance(EntityManager, targetXForm.Coordinates, out var dst) + || dst > leash.MaxDistance) + RemoveLeash(target, (leashEnt, leash)); + + // Calculate joint damage + if (_timing.CurTime < data.NextDamage + || !TryComp(target, out var jointComp) + || !jointComp.GetJoints.TryGetValue(data.JointId, out var joint)) + continue; + + // TODO reaction force always returns 0 and thus damage doesn't work + // TODO find another way to calculate how much force is being excerted to hold the two entities together + var damage = joint.GetReactionForce(1 / (float) leash.DamageInterval.TotalSeconds).Length() - leash.JointRepairDamage; + data.Damage = Math.Max(0f, data.Damage + damage); + data.NextDamage = _timing.CurTime + leash.DamageInterval; + + if (damage >= leash.BreakDamage && !_net.IsClient) + { + _popups.PopupPredicted(Loc.GetString("leash-snap-popup", ("leash", leashEnt)), target, null, PopupType.SmallCaution); + RemoveLeash(target, (leashEnt, leash), true); + } + } + } + + leashQuery.Dispose(); + } + + #region event handling + + private void OnAnchorUnequipping(Entity ent, ref BeingUnequippedAttemptEvent args) + { + // Prevent unequipping the anchor clothing until the leash is removed + if (TryComp(args.Equipment, out var leashed) && leashed.Puller is not null) + args.Cancel(); + } + + private void OnJointRemoved(Entity ent, ref JointRemovedEvent args) + { + var id = args.Joint.ID; + if (!ent.Comp.Leashed.TryFirstOrDefault(it => it.JointId == id, out var data) + || !TryComp(GetEntity(data.Pulled), out var leashed)) + return; + + RemoveLeash((leashed.Owner, leashed), ent!, false); + } + + private void OnGetEquipmentVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess + || !args.CanInteract + || args.Using is not { } leash + || !TryComp(leash, out var leashComp)) + return; + + var user = args.User; + var leashVerb = new EquipmentVerb { Text = Loc.GetString("verb-leash-text") }; + + if (CanLeash(ent, (leash, leashComp))) + leashVerb.Act = () => TryLeash(ent, (leash, leashComp), user); + else + { + 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 + return; + + var unleashVerb = new EquipmentVerb + { + Text = Loc.GetString("verb-unleash-text"), + Act = () => TryUnleash((leashTarget, leashedComp), (leash, leashComp), user) + }; + args.Verbs.Add(unleashVerb); + } + + private void OnGetLeashedVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess + || !args.CanInteract + || ent.Comp.Puller is not { } leash + || !TryComp(leash, out var leashComp)) + return; + + var user = args.User; + args.Verbs.Add(new InteractionVerb + { + Text = Loc.GetString("verb-unleash-text"), + Act = () => TryUnleash(ent!, (leash, leashComp), user) + }); + } + + private void OnAttachDoAfter(Entity ent, ref LeashAttachDoAfterEvent args) + { + if (args.Cancelled || args.Handled + || !TryComp(args.Used, out var leash) + || !CanLeash(ent, (args.Used.Value, leash))) + return; + + DoLeash(ent, (args.Used.Value, leash), args.Target!.Value); + } + + private void OnDetachDoAfter(Entity ent, ref LeashDetachDoAfterEvent args) + { + if (args.Cancelled || args.Handled) + return; + + RemoveLeash(args.Target!.Value, args.Used!.Value, true); + } + + #endregion + + #region private api + + /// + /// Tries to find the entity that gets leashed for the given anchor entity. + /// + private bool TryGetLeashTarget(Entity ent, out EntityUid leashTarget) + { + leashTarget = default; + if (TryComp(ent, out var clothing)) + { + if (clothing.InSlot == null || !_container.TryGetContainingContainer(ent, out var container)) + return false; + + leashTarget = container.Owner; + return true; + } + + leashTarget = ent.Owner; + return true; + } + + #endregion + + #region public api + + public bool CanLeash(Entity anchor, Entity leash) + { + return leash.Comp.Leashed.Count < leash.Comp.MaxJoints + && TryGetLeashTarget(anchor, out var leashTarget) + && CompOrNull(leashTarget)?.JointId == null + && Transform(anchor).Coordinates.TryDistance(EntityManager, Transform(leash).Coordinates, out var dst) + && dst <= leash.Comp.Length; + } + + public bool TryLeash(Entity anchor, Entity leash, EntityUid user) + { + if (!CanLeash(anchor, leash) || !TryGetLeashTarget(anchor, out var leashTarget)) + return false; + + // We reuse pulling attempt here because eugh it already exists + var attempt = new PullAttemptEvent(leash, anchor); + RaiseLocalEvent(anchor, attempt); + RaiseLocalEvent(leash, attempt); + + if (attempt.Cancelled) + return false; + + var doAfter = new DoAfterArgs(EntityManager, user, leash.Comp.AttachDelay, new LeashAttachDoAfterEvent(), anchor, leashTarget, leash) + { + BreakOnDamage = true, + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnWeightlessMove = true, + NeedHand = true + }; + return _doAfters.TryStartDoAfter(doAfter); + } + + public bool TryUnleash(Entity leashed, Entity leash, EntityUid user) + { + if (!Resolve(leashed, ref leashed.Comp, false) || !Resolve(leash, ref leash.Comp) || leashed.Comp.Puller != leash) + return false; + + var delay = user == leashed.Owner ? leash.Comp.SelfDetachDelay : leash.Comp.DetachDelay; + var doAfter = new DoAfterArgs(EntityManager, user, delay, new LeashDetachDoAfterEvent(), leashed.Owner, leashed) + { + BreakOnDamage = true, + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnWeightlessMove = true, + NeedHand = true + }; + + return _doAfters.TryStartDoAfter(doAfter); + } + + public void DoLeash(Entity anchor, Entity leash, EntityUid leashTarget) + { + if (_net.IsClient || leashTarget is { Valid: false } && !TryGetLeashTarget(anchor, out leashTarget)) + return; + + var leashedComp = EnsureComp(leashTarget); + var netLeashTarget = GetNetEntity(leashTarget); + leashedComp.JointId = $"leash-joint-{netLeashTarget}"; + leashedComp.Puller = leash; + + // 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 = _joints.CreateDistanceJoint(leash, leashTarget, id: leashedComp.JointId); + joint.CollideConnected = false; + joint.Length = leash.Comp.Length; + joint.MinLength = 0f; + joint.MaxLength = leash.Comp.Length; + joint.Stiffness = 0f; + joint.Damping = 0f; + + var data = new LeashComponent.LeashData(leashedComp.JointId, netLeashTarget) + { + NextDamage = _timing.CurTime + leash.Comp.DamageInterval + }; + + if (leash.Comp.LeashSprite is { } sprite) + { + var visualEntity = EntityManager.SpawnAttachedTo(null, Transform(leashTarget).Coordinates); + var visualComp = EnsureComp(visualEntity); + + visualComp.Sprite = sprite; + visualComp.Target = leash; + + data.LeashVisuals = GetNetEntity(visualEntity); + } + + leash.Comp.Leashed.Add(data); + Dirty(leash); + Dirty(leashTarget, leashedComp); + } + + public void RemoveLeash(Entity leashed, Entity leash, bool breakJoint = true) + { + if (_net.IsClient || !Resolve(leashed, ref leashed.Comp)) + return; + + var jointId = leashed.Comp.JointId; + RemComp(leashed); + + if (breakJoint && jointId is not null) + _joints.RemoveJoint(leash, jointId); + + if (Resolve(leash, ref leash.Comp, false)) + foreach (var data in leash.Comp.Leashed.Where(it => it.JointId == jointId).ToList()) + { + if (data.LeashVisuals is {} visualsEntity) + QueueDel(GetEntity(visualsEntity)); + + leash.Comp.Leashed.Remove(data); + } + + Dirty(leash); + } + + #endregion +} diff --git a/Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl b/Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl new file mode 100644 index 00000000000..583d000e1a7 --- /dev/null +++ b/Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl @@ -0,0 +1,3 @@ +verb-leash-text = Attach leash +verb-leash-error-message = The leash is already attached to something else, detach it first. +verb-unleash-text = Detach leash diff --git a/Resources/Locale/en-US/floofstation/leash/leash.ftl b/Resources/Locale/en-US/floofstation/leash/leash.ftl new file mode 100644 index 00000000000..ea91d878719 --- /dev/null +++ b/Resources/Locale/en-US/floofstation/leash/leash.ftl @@ -0,0 +1,8 @@ +leash-attaching-popup-target = {THE($user)} is trying to attach a leash to you! +leash-attaching-popup-others = {THE($user)} is trying to attach a leash to {THE($target)}. + +leash-attaching-popup-success-self = You attach {THE($leash)} to {THE($target)}. +leash-attaching-popup-success-target = {THE($user)} attaches {THE($leash)} to you. +leash-attaching-popup-success-others = {THE($user)} attaches {THE($leash)} to {THE($target)}. + +leash-snap-popup = {THE($leash)} snaps off! From c2d9f11ad1a069d5b525c1c399ae1c194eedb832 Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 18 Aug 2024 19:46:13 +0300 Subject: [PATCH 02/18] Add leash anchors to those things --- .../Floof/Entities/Clothing/Neck/collars.yml | 26 ++++++++++++------- .../Entities/Objects/Devices/shock_collar.yml | 1 + 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml index a0d64910eb9..c87b7b95d5c 100644 --- a/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml +++ b/Resources/Prototypes/Floof/Entities/Clothing/Neck/collars.yml @@ -1,5 +1,13 @@ - type: entity parent: ClothingNeckBase + id: ClothingNeckCollarBase + abstract: true + components: + - type: Clothing + - type: LeashAnchor + +- type: entity + parent: ClothingNeckCollarBase id: ClothingNeckCollarBlue name: blue collar description: A cute blue collar. @@ -10,7 +18,7 @@ sprite: Floof/Clothing/Neck/collar_blue.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarBlack name: black collar description: A cute black collar. @@ -21,7 +29,7 @@ sprite: Floof/Clothing/Neck/collar_black.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarEpi name: epistemics collar description: A collar showing how loyal you are to science! @@ -32,7 +40,7 @@ sprite: Floof/Clothing/Neck/collar_epi.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarEngi name: engineering collar description: A collar showing your deft skill utilizing your hands! @@ -43,7 +51,7 @@ sprite: Floof/Clothing/Neck/collar_engi.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarLogi name: logistics collar description: Who's a good courier? @@ -54,7 +62,7 @@ sprite: Floof/Clothing/Neck/collar_logi.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarMed name: medical collar description: Put your patients at ease by letting them know you're here to help. @@ -65,7 +73,7 @@ sprite: Floof/Clothing/Neck/collar_med.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarSec name: security collar description: Be the best guard dog on the station! @@ -76,7 +84,7 @@ sprite: Floof/Clothing/Neck/collar_sec.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarCmd name: Captain's collar description: The top dog (or cat) around. @@ -87,7 +95,7 @@ sprite: Floof/Clothing/Neck/collar_cmd.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarCC name: CentCom collar description: All bark with a whole lotta bite. @@ -98,7 +106,7 @@ sprite: Floof/Clothing/Neck/collar_cc.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingNeckCollarBase id: ClothingNeckCollarSyndi name: Blood-Red collar description: Sometimes you gotta be naughty. diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml index 1266a721fe2..bfec2afcf06 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/shock_collar.yml @@ -22,3 +22,4 @@ - type: DeviceLinkSink ports: - Trigger + - type: LeashAnchor # floofstation From 8e90f54f38624cc1a82a448c8ae4d70fce6f23da Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 18 Aug 2024 20:18:05 +0300 Subject: [PATCH 03/18] Popups and do-after stuff --- .../Leash/Events/LeashAttachDoAfterEvent.cs | 4 +- .../Leash/Events/LeashDetachDoAfterEvent.cs | 4 +- .../Floofstation/Leash/LeashSystem.cs | 46 +++++++++++++------ .../Locale/en-US/floofstation/leash/leash.ftl | 22 +++++++-- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs b/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs index 10406a003af..7ef567366d2 100644 --- a/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs +++ b/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs @@ -1,7 +1,9 @@ using Content.Shared.DoAfter; +using Robust.Shared.Serialization; namespace Content.Shared.Floofstation.Leash.Events; -public sealed class LeashAttachDoAfterEvent : SimpleDoAfterEvent +[Serializable, NetSerializable] +public sealed partial class LeashAttachDoAfterEvent : SimpleDoAfterEvent { } diff --git a/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs b/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs index 8a62dbebbfa..5f70cfa6642 100644 --- a/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs +++ b/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs @@ -1,7 +1,9 @@ using Content.Shared.DoAfter; +using Robust.Shared.Serialization; namespace Content.Shared.Floofstation.Leash.Events; -public sealed class LeashDetachDoAfterEvent : SimpleDoAfterEvent +[Serializable, NetSerializable] +public sealed partial class LeashDetachDoAfterEvent : SimpleDoAfterEvent { } diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index 35392bc0cd3..164852ff3cf 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -3,7 +3,6 @@ using Content.Shared.DoAfter; using Content.Shared.Floofstation.Leash.Components; using Content.Shared.Floofstation.Leash.Events; -using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Physics; @@ -13,6 +12,7 @@ using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; +using Robust.Shared.Player; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -67,15 +67,15 @@ public override void Update(float frameTime) // TODO reaction force always returns 0 and thus damage doesn't work // TODO find another way to calculate how much force is being excerted to hold the two entities together - var damage = joint.GetReactionForce(1 / (float) leash.DamageInterval.TotalSeconds).Length() - leash.JointRepairDamage; - data.Damage = Math.Max(0f, data.Damage + damage); - data.NextDamage = _timing.CurTime + leash.DamageInterval; - - if (damage >= leash.BreakDamage && !_net.IsClient) - { - _popups.PopupPredicted(Loc.GetString("leash-snap-popup", ("leash", leashEnt)), target, null, PopupType.SmallCaution); - RemoveLeash(target, (leashEnt, leash), true); - } + // var damage = joint.GetReactionForce(1 / (float) leash.DamageInterval.TotalSeconds).Length() - leash.JointRepairDamage; + // data.Damage = Math.Max(0f, data.Damage + damage); + // data.NextDamage = _timing.CurTime + leash.DamageInterval; + // + // if (damage >= leash.BreakDamage && !_net.IsClient) + // { + // _popups.PopupPredicted(Loc.GetString("leash-snap-popup", ("leash", leashEnt)), target, null, PopupType.SmallCaution); + // RemoveLeash(target, (leashEnt, leash), true); + // } } } @@ -227,10 +227,21 @@ public bool TryLeash(Entity anchor, Entity BreakOnWeightlessMove = true, NeedHand = true }; - return _doAfters.TryStartDoAfter(doAfter); + + var result = _doAfters.TryStartDoAfter(doAfter); + if (result && _net.IsServer) + { + (string, object)[] locArgs = [("user", user), ("target", leashTarget), ("anchor", anchor.Owner), ("selfAnchor", anchor.Owner == leashTarget)]; + + // This could've been much easier if my interaction verbs PR got merged already, but it isn't yet, so I gotta suffer + _popups.PopupEntity(Loc.GetString("leash-attaching-popup-target", locArgs), user, user); + _popups.PopupEntity(Loc.GetString("leash-attaching-popup-target", locArgs), leashTarget, leashTarget); + _popups.PopupEntity(Loc.GetString("leash-attaching-popup-others", locArgs), leashTarget, Filter.PvsExcept(leashTarget).RemovePlayerByAttachedEntity(user), true); + } + return result; } - public bool TryUnleash(Entity leashed, Entity leash, EntityUid user) + public bool TryUnleash(Entity leashed, Entity leash, EntityUid user, bool popup = true) { if (!Resolve(leashed, ref leashed.Comp, false) || !Resolve(leash, ref leash.Comp) || leashed.Comp.Puller != leash) return false; @@ -245,7 +256,15 @@ public bool TryUnleash(Entity leashed, Entity anchor, Entity leash, EntityUid leashTarget) @@ -285,7 +304,6 @@ public void DoLeash(Entity anchor, Entity leash.Comp.Leashed.Add(data); Dirty(leash); - Dirty(leashTarget, leashedComp); } public void RemoveLeash(Entity leashed, Entity leash, bool breakJoint = true) diff --git a/Resources/Locale/en-US/floofstation/leash/leash.ftl b/Resources/Locale/en-US/floofstation/leash/leash.ftl index ea91d878719..0ac8a8e0d28 100644 --- a/Resources/Locale/en-US/floofstation/leash/leash.ftl +++ b/Resources/Locale/en-US/floofstation/leash/leash.ftl @@ -1,8 +1,20 @@ -leash-attaching-popup-target = {THE($user)} is trying to attach a leash to you! -leash-attaching-popup-others = {THE($user)} is trying to attach a leash to {THE($target)}. +leash-attaching-popup-self = You are trying to attach a leash to {$selfAnchor -> + [false] {THE($target)}'s {$anchor} + *[true] {THE($target)} +}... +leash-attaching-popup-target = {THE($target)} is trying to attach a leash to {$selfAnchor -> + [false] your {$anchor} + *[true] you +}... +leash-attaching-popup-others = {THE($user)} is trying to attach a leash to {$selfAnchor -> + [false] {THE($target)}'s {$anchor} + *[true] {THE($target)} +} -leash-attaching-popup-success-self = You attach {THE($leash)} to {THE($target)}. -leash-attaching-popup-success-target = {THE($user)} attaches {THE($leash)} to you. -leash-attaching-popup-success-others = {THE($user)} attaches {THE($leash)} to {THE($target)}. +leash-detaching-popup-self = You begin trying to remove the leash... +leash-detaching-popup-others = {THE($user)} is trying to remove the leash {$isSelf -> + [true] from {REFLEXIVE($user)} + *[false] from {THE($target)} +}... leash-snap-popup = {THE($leash)} snaps off! From 5adf788c9b969fb1802922175aa8bb048d1c7180 Mon Sep 17 00:00:00 2001 From: fox Date: Sun, 18 Aug 2024 21:21:33 +0300 Subject: [PATCH 04/18] Dragging + fixes --- .../Leash/Components/LeashComponent.cs | 10 ++- .../Floofstation/Leash/LeashSystem.cs | 68 +++++++++++++++++-- .../Locale/en-US/floofstation/leash/leash.ftl | 2 +- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs index 59714dfd941..03f31146593 100644 --- a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs +++ b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs @@ -26,13 +26,13 @@ public sealed partial class LeashComponent : Component public float MaxDistance = 8f; /// - /// The time it takes for one entity to attach the leash to another entity. + /// The time it takes for one entity to attach/detach the leash to/from another entity. /// [DataField, AutoNetworkedField] public TimeSpan AttachDelay = TimeSpan.FromSeconds(2f), DetachDelay = TimeSpan.FromSeconds(2f); /// - /// The time it takes for the leashed entity to detach itself. + /// The time it takes for the leashed entity to detach itself from this leash. /// [DataField, AutoNetworkedField] public TimeSpan SelfDetachDelay = TimeSpan.FromSeconds(8f); @@ -40,6 +40,12 @@ public sealed partial class LeashComponent : Component [DataField, AutoNetworkedField] public SpriteSpecifier? LeashSprite; + [DataField] + public TimeSpan NextPull = TimeSpan.Zero; + + [DataField, AutoNetworkedField] + public TimeSpan PullInterval = TimeSpan.FromSeconds(2.5f); + /// /// How much damage each leash joint can sustain before it breaks. /// diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index 164852ff3cf..ad9f3a6375d 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -3,12 +3,19 @@ using Content.Shared.DoAfter; using Content.Shared.Floofstation.Leash.Components; using Content.Shared.Floofstation.Leash.Events; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Input; using Content.Shared.Inventory.Events; using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Throwing; using Content.Shared.Verbs; using Robust.Shared.Containers; +using Robust.Shared.Input.Binding; +using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; @@ -26,6 +33,8 @@ public sealed class LeashSystem : EntitySystem [Dependency] private readonly SharedJointSystem _joints = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; public override void Initialize() { @@ -38,6 +47,16 @@ public override void Initialize() SubscribeLocalEvent(OnAttachDoAfter); SubscribeLocalEvent(OnDetachDoAfter); + + CommandBinds.Builder + .BindBefore(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(OnRequestPullLeash), before: [typeof(PullingSystem)]) + .Register(); + } + + public override void Shutdown() + { + base.Shutdown(); + CommandBinds.Unregister(); } public override void Update(float frameTime) @@ -164,10 +183,44 @@ private void OnAttachDoAfter(Entity ent, ref LeashAttachDo private void OnDetachDoAfter(Entity ent, ref LeashDetachDoAfterEvent args) { - if (args.Cancelled || args.Handled) + if (args.Cancelled || args.Handled || ent.Comp.Puller is not { } leash) return; - RemoveLeash(args.Target!.Value, args.Used!.Value, true); + RemoveLeash(ent!, leash, true); + } + + private bool OnRequestPullLeash(ICommonSession? session, EntityCoordinates targetCoords, EntityUid uid) + { + if (session?.AttachedEntity is not { } player + || !player.IsValid() + || !TryComp(player, out var hands) + || hands.ActiveHandEntity is not {} leash + || !TryComp(leash, out var leashComp) + || leashComp.NextPull > _timing.CurTime) + return false; + + // Pull all entities towards the coordinates. + targetCoords = targetCoords.WithEntityId(player); + var userCoords = Transform(player).Coordinates; + foreach (var data in leashComp.Leashed) + { + var pulled = GetEntity(data.Pulled); + var pulledCoords = Transform(pulled).Coordinates.WithEntityId(player); + + // Ensure that the new entity position is actually closer to the user than previous - this is to limit pushing via a leash + var newCoords = targetCoords; + if (!userCoords.TryDistance(EntityManager, _xform, pulledCoords, out var sourceDst) + || !userCoords.TryDelta(EntityManager, _xform, targetCoords, out var userTargetDelta)) + continue; + + if (userTargetDelta.Length() > sourceDst) + newCoords = userCoords.WithPosition(userTargetDelta.Normalized() * sourceDst); + + _throwing.TryThrow(pulled, newCoords, user: player, animated: false, playSound: false, doSpin: false, pushbackRatio: 1f); + } + + leashComp.NextPull = _timing.CurTime + leashComp.PullInterval; + return true; } #endregion @@ -234,9 +287,12 @@ public bool TryLeash(Entity anchor, Entity (string, object)[] locArgs = [("user", user), ("target", leashTarget), ("anchor", anchor.Owner), ("selfAnchor", anchor.Owner == leashTarget)]; // This could've been much easier if my interaction verbs PR got merged already, but it isn't yet, so I gotta suffer - _popups.PopupEntity(Loc.GetString("leash-attaching-popup-target", locArgs), user, user); - _popups.PopupEntity(Loc.GetString("leash-attaching-popup-target", locArgs), leashTarget, leashTarget); - _popups.PopupEntity(Loc.GetString("leash-attaching-popup-others", locArgs), leashTarget, Filter.PvsExcept(leashTarget).RemovePlayerByAttachedEntity(user), true); + _popups.PopupEntity(Loc.GetString("leash-attaching-popup-self", locArgs), user, user); + if (user != leashTarget) + _popups.PopupEntity(Loc.GetString("leash-attaching-popup-target", locArgs), leashTarget, leashTarget); + + var othersFilter = Filter.PvsExcept(leashTarget).RemovePlayerByAttachedEntity(user); + _popups.PopupEntity(Loc.GetString("leash-attaching-popup-others", locArgs), leashTarget, othersFilter, true); } return result; } @@ -312,7 +368,7 @@ public void RemoveLeash(Entity leashed, Entity(leashed); + RemCompDeferred(leashed); // Has to be deferred else the client explodes for some reason if (breakJoint && jointId is not null) _joints.RemoveJoint(leash, jointId); diff --git a/Resources/Locale/en-US/floofstation/leash/leash.ftl b/Resources/Locale/en-US/floofstation/leash/leash.ftl index 0ac8a8e0d28..4d1a0a7f509 100644 --- a/Resources/Locale/en-US/floofstation/leash/leash.ftl +++ b/Resources/Locale/en-US/floofstation/leash/leash.ftl @@ -11,7 +11,7 @@ leash-attaching-popup-others = {THE($user)} is trying to attach a leash to {$sel *[true] {THE($target)} } -leash-detaching-popup-self = You begin trying to remove the leash... +leash-detaching-popup-self = You are trying to remove the leash... leash-detaching-popup-others = {THE($user)} is trying to remove the leash {$isSelf -> [true] from {REFLEXIVE($user)} *[false] from {THE($target)} From aa0bc3edbbfed37c39afa0aa5823e890ac8186f5 Mon Sep 17 00:00:00 2001 From: fox Date: Mon, 19 Aug 2024 17:12:09 +0300 Subject: [PATCH 05/18] Entities & sprites --- .../Floof/Entities/Objects/Tools/leash.yml | 49 ++++++++++++++++++ .../Objects/Tools/leash-rope.rsi/meta.json | 14 +++++ .../Objects/Tools/leash-rope.rsi/rope.png | Bin 0 -> 1121 bytes .../Floof/Objects/Tools/leash.rsi/icon.png | Bin 0 -> 1115 bytes .../Floof/Objects/Tools/leash.rsi/meta.json | 14 +++++ 5 files changed, 77 insertions(+) create mode 100644 Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml create mode 100644 Resources/Textures/Floof/Objects/Tools/leash-rope.rsi/meta.json create mode 100644 Resources/Textures/Floof/Objects/Tools/leash-rope.rsi/rope.png create mode 100644 Resources/Textures/Floof/Objects/Tools/leash.rsi/icon.png create mode 100644 Resources/Textures/Floof/Objects/Tools/leash.rsi/meta.json diff --git a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml new file mode 100644 index 00000000000..689ca8ffc0c --- /dev/null +++ b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml @@ -0,0 +1,49 @@ +- type: entity + id: BaseLeash + parent: BaseItem + name: leash + description: Helps keep your animals close to you, as well as your friends. Attach to supported object or clothing (such as collars) to use. You can pull attached entities while holding the leash. + noSpawn: true + components: + - type: Sprite + sprite: Floof/Objects/Tools/leash.rsi + layers: + - state: icon + - type: Leash + leashSprite: + sprite: Floof/Objects/Tools/leash-rope.rsi + state: rope + +- type: entity + id: LeashBasic + parent: BaseLeash + components: + - type: Leash + length: 3.5 + attachDelay: 4.5 # Gotta be at least as high as cuffs or antags may abuse it + detachDelay: 3 + selfDetachDelay: 10 + +- type: entity + id: LeashAdvanced + parent: LeashBasic + name: advanced leash + components: + - type: Leash + maxJoints: 3 + attachDelay: 2.5 + detachDelay: 2 + selfDetachDelay: 15 + +- type: entity + id: LeashBluespace + parent: BaseLeash + name: bluespace leash + description: Powered by a miniature singularity inside the handle. Not safe for use by crewmembers. + suffix: DEBUG, DO NOT MAP + components: + - type: Leash + maxJoints: 25 + attachDelay: 0 + detachDelay: 10000 # will still be instant for admin ghosts or whatever with instant doafters tag + selfDetachDelay: 10000 diff --git a/Resources/Textures/Floof/Objects/Tools/leash-rope.rsi/meta.json b/Resources/Textures/Floof/Objects/Tools/leash-rope.rsi/meta.json new file mode 100644 index 00000000000..0efc53a9e93 --- /dev/null +++ b/Resources/Textures/Floof/Objects/Tools/leash-rope.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Mnemotechnician (github) for SS14", + "size": { + "x": 8, + "y": 64 + }, + "states": [ + { + "name": "rope" + } + ] +} diff --git a/Resources/Textures/Floof/Objects/Tools/leash-rope.rsi/rope.png b/Resources/Textures/Floof/Objects/Tools/leash-rope.rsi/rope.png new file mode 100644 index 0000000000000000000000000000000000000000..46bfe23e2ab511ecbc405e0895186cf0934d4a74 GIT binary patch literal 1121 zcmZvac}Uek5XUEzdKiY$0Uco3MwgV4g%}+kq4N?mdu0Ve&_&6BL`F%6RF>L5F$SFk zMM;-JLPM9)@ka&S2NBZ=MTccb{ZUeE^Ii}>Uq9I0*&jRm`ObH?wx}>KF=0>w03>35 zZn3Qa8#4kuY#tduxelN^6cvPK+SW&gk1XDHKK!Ywh%hq|A)<3m0R(^n2mk?`WySnb z1rcBdB0vQ9ze5TtAPh`E29{9%_h~WsIV|G zA)!MjURargkkOH2&!mKcjvaYIwG$3Kx=6li7aEFm1`_i>rup+1Yg)o&nLc+*odf6+fps>|JUQxyBL$6kMI6E3<&UEIP zf&Y-!+`m+lV&Diq#yN;~&bFOP2{+lZD6t^1LjtE=yap56$!;LgE*Z$;i51Cuz~Csl z-0&M=FQklOokJk2Gx{|kMEA!;f-(q!9rqv#EI@3}Ry?qp1TawKyEi5-9-!M0%*_hr z-*^|Am2fSs-@%5dwU>_VDXOfhsUF%?S5iLq!;$wNf}v6AWqnH5#kbV8o~v_{C(Qem zy5S;!zjI*y#M+~&hs!b!9#3sul0GQ!(aLVHIPmzu_@xVzw`P5Ty5-ysDR5C$e71ax*&N?I!3izu@YF(NtVnUzQo*i zz<)?nH(;v6J9XWN#g#GC9{lvt41A%W9YUV{m3VKG2?XHLq&v=+LH1B_XPqI6F{x vGXF+lx6H-UdOZD}H}Ck558*Li7JN?IdHK-I2V)B|;#OhK}L2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Floof/Objects/Tools/leash.rsi/meta.json b/Resources/Textures/Floof/Objects/Tools/leash.rsi/meta.json new file mode 100644 index 00000000000..ade27d844e5 --- /dev/null +++ b/Resources/Textures/Floof/Objects/Tools/leash.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Mnemotechnician (github) for SS14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + } + ] +} From d4a9795e59d5013a5bdbd2463e0e30357a6c1bdf Mon Sep 17 00:00:00 2001 From: fox Date: Mon, 19 Aug 2024 18:20:44 +0300 Subject: [PATCH 06/18] FIXES --- Content.Client/Physics/JointVisualsOverlay.cs | 4 + .../Leash/Components/LeashComponent.cs | 6 +- .../Leash/Components/LeashedComponent.cs | 2 + .../Floofstation/Leash/LeashSystem.cs | 87 +++++++++++-------- .../Floof/Entities/Objects/Tools/leash.yml | 1 + 5 files changed, 60 insertions(+), 40 deletions(-) diff --git a/Content.Client/Physics/JointVisualsOverlay.cs b/Content.Client/Physics/JointVisualsOverlay.cs index 09c02746e2e..5c61338aaba 100644 --- a/Content.Client/Physics/JointVisualsOverlay.cs +++ b/Content.Client/Physics/JointVisualsOverlay.cs @@ -1,7 +1,9 @@ +using System.Numerics; using Content.Shared.Physics; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; +using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Dynamics.Joints; @@ -27,6 +29,8 @@ protected override void Draw(in OverlayDrawArgs args) { _drawn.Clear(); var worldHandle = args.WorldHandle; + // Floofstation: fix incorrect drawing box location due to incorrect coordinate system + worldHandle.SetTransform(Vector2.Zero, Angle.Zero); var spriteSystem = _entManager.System(); var xformSystem = _entManager.System(); diff --git a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs index 03f31146593..01922720ceb 100644 --- a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs +++ b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs @@ -44,24 +44,26 @@ public sealed partial class LeashComponent : Component public TimeSpan NextPull = TimeSpan.Zero; [DataField, AutoNetworkedField] - public TimeSpan PullInterval = TimeSpan.FromSeconds(2.5f); + public TimeSpan PullInterval = TimeSpan.FromSeconds(1.5f); /// /// How much damage each leash joint can sustain before it breaks. /// - /// Damage here actually refers to impulse exerted by the joint minus repair. + /// Not currently implemented; needs to be reworked in order to work. [DataField, AutoNetworkedField] public float BreakDamage = 20f; /// /// How much damage each leash joint loses every . /// + /// Not currently implemented; needs to be reworked in order to work. [DataField, AutoNetworkedField] public float JointRepairDamage = 1f; /// /// Interval at which damage is calculated for each joint. /// + /// Not currently implemented; needs to be reworked in order to work. [DataField, AutoNetworkedField] public TimeSpan DamageInterval = TimeSpan.FromMilliseconds(200); diff --git a/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs index f0b249286b4..9ed7b818d69 100644 --- a/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs +++ b/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs @@ -3,6 +3,8 @@ namespace Content.Shared.Floofstation.Leash.Components; [RegisterComponent] public sealed partial class LeashedComponent : Component { + public const string VisualsContainerName = "leashed-visuals"; + [DataField] public string? JointId = null; diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index ad9f3a6375d..eaf01ca9627 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -69,14 +69,15 @@ public override void Update(float frameTime) foreach (var data in leash.Leashed) { - // Break each leash joint whose entities are on different maps or are too far apart - var target = GetEntity(data.Pulled); - var targetXForm = Transform(target); + if (data.Pulled == NetEntity.Invalid || !TryGetEntity(data.Pulled, out var target)) + 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, (leashEnt, leash)); + RemoveLeash(target.Value, (leashEnt, leash)); // Calculate joint damage if (_timing.CurTime < data.NextDamage @@ -106,7 +107,10 @@ public override void Update(float frameTime) private void OnAnchorUnequipping(Entity ent, ref BeingUnequippedAttemptEvent args) { // Prevent unequipping the anchor clothing until the leash is removed - if (TryComp(args.Equipment, out var leashed) && leashed.Puller is not null) + if (TryGetLeashTarget(args.Equipment, out var leashTarget) + && TryComp(leashTarget, out var leashed) + && leashed.Puller is not null + ) args.Cancel(); } @@ -141,7 +145,7 @@ private void OnGetEquipmentVerbs(Entity ent, ref GetVerbsE args.Verbs.Add(leashVerb); - if (!TryGetLeashTarget(ent, out var leashTarget) + 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 @@ -199,25 +203,26 @@ private bool OnRequestPullLeash(ICommonSession? session, EntityCoordinates targe || leashComp.NextPull > _timing.CurTime) return false; - // Pull all entities towards the coordinates. - targetCoords = targetCoords.WithEntityId(player); - var userCoords = Transform(player).Coordinates; - foreach (var data in leashComp.Leashed) - { - var pulled = GetEntity(data.Pulled); - var pulledCoords = Transform(pulled).Coordinates.WithEntityId(player); + // find the entity closest to the target coords + var candidates = leashComp.Leashed + .Select(it => GetEntity(it.Pulled)) + .Where(it => it != EntityUid.Invalid) + .Select(it => (it, Transform(it).Coordinates.TryDistance(EntityManager, _xform, targetCoords, out var dist) ? dist : float.PositiveInfinity)) + .Where(it => it.Item2 < float.PositiveInfinity) + .ToList(); - // Ensure that the new entity position is actually closer to the user than previous - this is to limit pushing via a leash - var newCoords = targetCoords; - if (!userCoords.TryDistance(EntityManager, _xform, pulledCoords, out var sourceDst) - || !userCoords.TryDelta(EntityManager, _xform, targetCoords, out var userTargetDelta)) - continue; + if (candidates.Count == 0) + return false; - if (userTargetDelta.Length() > sourceDst) - newCoords = userCoords.WithPosition(userTargetDelta.Normalized() * sourceDst); + // And pull it towards the user + var pulled = candidates.MinBy(it => it.Item2).Item1; + var playerCoords = Transform(player).Coordinates; + var pulledCoords = Transform(pulled).Coordinates; + if (!playerCoords.TryDelta(EntityManager, _xform, pulledCoords, out var delta)) + return false; - _throwing.TryThrow(pulled, newCoords, user: player, animated: false, playSound: false, doSpin: false, pushbackRatio: 1f); - } + var pullTarget = playerCoords.WithPosition(delta.Normalized() * 0.5f); + _throwing.TryThrow(pulled, pullTarget, user: player, pushbackRatio: 1f, strength: 1.5f, animated: false, recoil: false, playSound: false, doSpin: false); leashComp.NextPull = _timing.CurTime + leashComp.PullInterval; return true; @@ -230,9 +235,12 @@ private bool OnRequestPullLeash(ICommonSession? session, EntityCoordinates targe /// /// Tries to find the entity that gets leashed for the given anchor entity. /// - private bool TryGetLeashTarget(Entity ent, out EntityUid leashTarget) + private bool TryGetLeashTarget(Entity ent, out EntityUid leashTarget) { leashTarget = default; + if (!Resolve(ent, ref ent.Comp, false)) + return false; + if (TryComp(ent, out var clothing)) { if (clothing.InSlot == null || !_container.TryGetContainingContainer(ent, out var container)) @@ -253,7 +261,7 @@ private bool TryGetLeashTarget(Entity ent, out EntityUid l public bool CanLeash(Entity anchor, Entity leash) { return leash.Comp.Leashed.Count < leash.Comp.MaxJoints - && TryGetLeashTarget(anchor, out var leashTarget) + && TryGetLeashTarget(anchor!, out var leashTarget) && CompOrNull(leashTarget)?.JointId == null && Transform(anchor).Coordinates.TryDistance(EntityManager, Transform(leash).Coordinates, out var dst) && dst <= leash.Comp.Length; @@ -261,7 +269,7 @@ public bool CanLeash(Entity anchor, Entity public bool TryLeash(Entity anchor, Entity leash, EntityUid user) { - if (!CanLeash(anchor, leash) || !TryGetLeashTarget(anchor, out var leashTarget)) + if (!CanLeash(anchor, leash) || !TryGetLeashTarget(anchor!, out var leashTarget)) return false; // We reuse pulling attempt here because eugh it already exists @@ -325,7 +333,7 @@ public bool TryUnleash(Entity leashed, Entity anchor, Entity leash, EntityUid leashTarget) { - if (_net.IsClient || leashTarget is { Valid: false } && !TryGetLeashTarget(anchor, out leashTarget)) + if (_net.IsClient || leashTarget is { Valid: false } && !TryGetLeashTarget(anchor!, out leashTarget)) return; var leashedComp = EnsureComp(leashTarget); @@ -349,13 +357,15 @@ public void DoLeash(Entity anchor, Entity if (leash.Comp.LeashSprite is { } sprite) { - var visualEntity = EntityManager.SpawnAttachedTo(null, Transform(leashTarget).Coordinates); - var visualComp = EnsureComp(visualEntity); - - visualComp.Sprite = sprite; - visualComp.Target = leash; + _container.EnsureContainer(leashTarget, LeashedComponent.VisualsContainerName); + if (EntityManager.TrySpawnInContainer(null, leashTarget, LeashedComponent.VisualsContainerName, out var visualEntity)) + { + var visualComp = EnsureComp(visualEntity.Value); + visualComp.Sprite = sprite; + visualComp.Target = leash; - data.LeashVisuals = GetNetEntity(visualEntity); + data.LeashVisuals = GetNetEntity(visualEntity); + } } leash.Comp.Leashed.Add(data); @@ -370,17 +380,18 @@ public void RemoveLeash(Entity leashed, Entity(leashed); // Has to be deferred else the client explodes for some reason + if (_container.TryGetContainer(leashed, LeashedComponent.VisualsContainerName, out var visualsContainer)) + _container.CleanContainer(visualsContainer); + if (breakJoint && jointId is not null) _joints.RemoveJoint(leash, jointId); if (Resolve(leash, ref leash.Comp, false)) - foreach (var data in leash.Comp.Leashed.Where(it => it.JointId == jointId).ToList()) - { - if (data.LeashVisuals is {} visualsEntity) - QueueDel(GetEntity(visualsEntity)); - + { + var leashedData = leash.Comp.Leashed.Where(it => it.JointId == jointId).ToList(); + foreach (var data in leashedData) leash.Comp.Leashed.Remove(data); - } + } Dirty(leash); } diff --git a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml index 689ca8ffc0c..5d09435d66f 100644 --- a/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml +++ b/Resources/Prototypes/Floof/Entities/Objects/Tools/leash.yml @@ -47,3 +47,4 @@ attachDelay: 0 detachDelay: 10000 # will still be instant for admin ghosts or whatever with instant doafters tag selfDetachDelay: 10000 + pullInterval: 0.1 From 7a7ada0f2f3cb5eeff6e010876be9076b5f2d4f1 Mon Sep 17 00:00:00 2001 From: fox Date: Mon, 19 Aug 2024 18:38:34 +0300 Subject: [PATCH 07/18] Add leash anchors to most animals --- .../Prototypes/Entities/Mobs/NPCs/animals.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 29234ea34cf..cfea8af020e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -254,6 +254,7 @@ factions: - Passive - type: RandomBark + - type: LeashAnchor # Floofstation - type: entity parent: MobChicken @@ -561,7 +562,7 @@ - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning - + - type: LeashAnchor # Floofstation - moffroach my beloved # Note that the mallard duck is actually a male drake mallard, with the brown duck being the female variant of the same species, however ss14 lacks sex specific textures # The white duck is more akin to a pekin or call duck. @@ -642,6 +643,7 @@ - Passive - type: RandomBark barkMultiplier: 0.7 + - type: LeashAnchor # Floofstation - type: entity name: white duck #Quack @@ -829,6 +831,7 @@ - Moooooo - Moooo barkMultiplier: 3 + - type: LeashAnchor # Floofstation - type: entity @@ -997,6 +1000,7 @@ - type: HTN rootTask: task: RuminantHostileCompound + - type: LeashAnchor # Floofstation # Note that we gotta make this bitch vomit someday when you feed it anthrax or sumthin. Needs to be a small item thief too and aggressive if attacked. - type: entity @@ -1045,6 +1049,7 @@ - type: NpcFactionMember factions: - Passive + - type: LeashAnchor # Floofstation - type: entity name: gorilla @@ -1100,6 +1105,7 @@ - type: HTN rootTask: task: SimpleHostileCompound + - type: LeashAnchor # Floofstation - type: entity name: kangaroo @@ -1192,6 +1198,7 @@ - type: HTN rootTask: task: SimpleHostileCompound + - type: LeashAnchor # Floofstation - type: entity name: boxing kangaroo @@ -1313,6 +1320,7 @@ tags: - VimPilot - DoorBumpOpener + - type: LeashAnchor # Floofstation - type: entity name: monkey @@ -1822,6 +1830,7 @@ - type: Tag tags: - VimPilot + - type: LeashAnchor # Floofstation - type: entity @@ -1870,6 +1879,7 @@ interactSuccessSpawn: EffectHearts - type: Bloodstream bloodMaxVolume: 50 + - type: LeashAnchor # Floofstation - type: entity name: frog @@ -2446,6 +2456,7 @@ - Hissing understands: - Hissing + - type: LeashAnchor # Floofstation - type: entity @@ -2529,6 +2540,7 @@ - Hissing understands: - Hissing + - type: LeashAnchor # Floofstation - type: entity name: fox @@ -2617,6 +2629,7 @@ - Fox understands: - Fox + - type: LeashAnchor # Floofstation - type: entity name: corgi @@ -2686,6 +2699,7 @@ tags: - VimPilot - type: RandomBark + - type: LeashAnchor # Floofstation - type: entity name: corrupted corgi @@ -2847,6 +2861,7 @@ tags: - VimPilot - type: RandomBark + - type: LeashAnchor # Floofstation - type: entity name: calico cat @@ -3139,6 +3154,7 @@ - Hissing understands: - Hissing + - type: LeashAnchor # Floofstation - type: entity name: hamster @@ -3285,6 +3301,7 @@ - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning + - type: LeashAnchor # Floofstation - type: entity name: pig From 3bb6afc42af4db49f90b9a8eab59a7a231c65a7c Mon Sep 17 00:00:00 2001 From: fox Date: Mon, 19 Aug 2024 18:54:22 +0300 Subject: [PATCH 08/18] Make craftable in autolathe --- .../Prototypes/Entities/Structures/Machines/lathe.yml | 1 + Resources/Prototypes/Floof/Recipes/Lathes/tools.yml | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 Resources/Prototypes/Floof/Recipes/Lathes/tools.yml diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 46251740b22..aae62b3d3e4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -162,6 +162,7 @@ - HandheldStationMap - ClothingHeadHatWelding - CustomDrinkJug # FloofStation + - LeashBasic # FloofStation - type: EmagLatheRecipes emagStaticRecipes: - CartridgePistol diff --git a/Resources/Prototypes/Floof/Recipes/Lathes/tools.yml b/Resources/Prototypes/Floof/Recipes/Lathes/tools.yml new file mode 100644 index 00000000000..83e86c43658 --- /dev/null +++ b/Resources/Prototypes/Floof/Recipes/Lathes/tools.yml @@ -0,0 +1,9 @@ +- type: latheRecipe + id: LeashBasic + result: LeashBasic + completetime: 3.5 + materials: + Cloth: 50 + Plastic: 500 + Steel: 75 + From 8548f8468c4f3130d9d6f197d1fb88672968b07e Mon Sep 17 00:00:00 2001 From: fox Date: Mon, 19 Aug 2024 19:39:32 +0300 Subject: [PATCH 09/18] More fixes --- .../Leash/Components/LeashedComponent.cs | 2 +- .../Floofstation/Leash/LeashSystem.cs | 25 ++++++++++++++++--- .../en-US/floofstation/leash/leash-verbs.ftl | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs index 9ed7b818d69..616b33404cd 100644 --- a/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs +++ b/Content.Shared/Floofstation/Leash/Components/LeashedComponent.cs @@ -9,5 +9,5 @@ public sealed partial class LeashedComponent : Component public string? JointId = null; [NonSerialized] - public EntityUid? Puller = null; + public EntityUid? Puller = null, Anchor = null; } diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index eaf01ca9627..89aa1a9b334 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -41,6 +41,7 @@ public override void Initialize() UpdatesBefore.Add(typeof(SharedPhysicsSystem)); SubscribeLocalEvent(OnAnchorUnequipping); + SubscribeLocalEvent(OnLeashedInserting); SubscribeLocalEvent(OnJointRemoved); SubscribeLocalEvent>(OnGetEquipmentVerbs); SubscribeLocalEvent>(OnGetLeashedVerbs); @@ -67,7 +68,7 @@ public override void Update(float frameTime) { var sourceXForm = Transform(leashEnt); - foreach (var data in leash.Leashed) + foreach (var data in leash.Leashed.ToList()) { if (data.Pulled == NetEntity.Invalid || !TryGetEntity(data.Pulled, out var target)) continue; @@ -114,6 +115,22 @@ private void OnAnchorUnequipping(Entity ent, ref BeingUneq args.Cancel(); } + private void OnLeashedInserting(Entity ent, ref ContainerGettingInsertedAttemptEvent args) + { + // Prevent the entity from entering crates and the like because that would instantly break all joints on it, including the leash + if (!Exists(ent.Comp.Puller) + || !Exists(ent.Comp.Anchor) + || !TryComp(ent.Comp.Puller, out var leashPuller) + || !TryComp(ent.Comp.Anchor, out var leashAnchor)) + return; + + args.Cancel(); + // This is hella unsafe to do, but we recreate the joint because dumb storage system removes it before raising the event. + // We have to pray that OnJointRemoved already was called and that it deferred the removal of everything that used to exist + // I HATE STORAGE + DoLeash((ent.Comp.Anchor.Value, leashAnchor), (ent.Comp.Puller.Value, leashPuller), ent); + } + private void OnJointRemoved(Entity ent, ref JointRemovedEvent args) { var id = args.Joint.ID; @@ -221,7 +238,7 @@ private bool OnRequestPullLeash(ICommonSession? session, EntityCoordinates targe if (!playerCoords.TryDelta(EntityManager, _xform, pulledCoords, out var delta)) return false; - var pullTarget = playerCoords.WithPosition(delta.Normalized() * 0.5f); + var pullTarget = playerCoords.WithPosition(delta.Normalized() * 0.5f).ToMapPos(EntityManager, _xform); _throwing.TryThrow(pulled, pullTarget, user: player, pushbackRatio: 1f, strength: 1.5f, animated: false, recoil: false, playSound: false, doSpin: false); leashComp.NextPull = _timing.CurTime + leashComp.PullInterval; @@ -264,7 +281,8 @@ public bool CanLeash(Entity anchor, Entity && TryGetLeashTarget(anchor!, out var leashTarget) && CompOrNull(leashTarget)?.JointId == null && Transform(anchor).Coordinates.TryDistance(EntityManager, Transform(leash).Coordinates, out var dst) - && dst <= leash.Comp.Length; + && dst <= leash.Comp.Length + && !_xform.IsParentOf(Transform(leashTarget), leash); // google recursion - this makes the game explode for some reason } public bool TryLeash(Entity anchor, Entity leash, EntityUid user) @@ -340,6 +358,7 @@ public void DoLeash(Entity anchor, Entity var netLeashTarget = GetNetEntity(leashTarget); leashedComp.JointId = $"leash-joint-{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 = _joints.CreateDistanceJoint(leash, leashTarget, id: leashedComp.JointId); diff --git a/Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl b/Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl index 583d000e1a7..61d83139512 100644 --- a/Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl +++ b/Resources/Locale/en-US/floofstation/leash/leash-verbs.ftl @@ -1,3 +1,3 @@ verb-leash-text = Attach leash -verb-leash-error-message = The leash is already attached to something else, detach it first. +verb-leash-error-message = Cannot attach the leash to this anchor. verb-unleash-text = Detach leash From 101cd30682027b1615f2a1a64f76a1777daed1cb Mon Sep 17 00:00:00 2001 From: fox Date: Mon, 19 Aug 2024 20:28:41 +0300 Subject: [PATCH 10/18] FINALLY FIX PULLING GAH --- Content.Shared/Floofstation/Leash/LeashSystem.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index 89aa1a9b334..a771204d606 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -235,11 +235,9 @@ private bool OnRequestPullLeash(ICommonSession? session, EntityCoordinates targe var pulled = candidates.MinBy(it => it.Item2).Item1; var playerCoords = Transform(player).Coordinates; var pulledCoords = Transform(pulled).Coordinates; - if (!playerCoords.TryDelta(EntityManager, _xform, pulledCoords, out var delta)) - return false; + var pullDir = _xform.ToMapCoordinates(playerCoords).Position - _xform.ToMapCoordinates(pulledCoords).Position; - var pullTarget = playerCoords.WithPosition(delta.Normalized() * 0.5f).ToMapPos(EntityManager, _xform); - _throwing.TryThrow(pulled, pullTarget, user: player, pushbackRatio: 1f, strength: 1.5f, animated: false, recoil: false, playSound: false, doSpin: false); + _throwing.TryThrow(pulled, pullDir * 0.5f, user: player, pushbackRatio: 1f, strength: 3f, animated: false, recoil: false, playSound: false, doSpin: false); leashComp.NextPull = _timing.CurTime + leashComp.PullInterval; return true; From 564b7febd6dd1460782321d98ec0a5e701e9595f Mon Sep 17 00:00:00 2001 From: fox Date: Tue, 20 Aug 2024 11:39:25 +0300 Subject: [PATCH 11/18] Shuffle files around --- .../Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs | 9 --------- .../LeashAttachDoAfterEvent.cs => LeashDoAfterEvents.cs} | 7 ++++++- Content.Shared/Floofstation/Leash/LeashSystem.cs | 2 -- 3 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs rename Content.Shared/Floofstation/Leash/{Events/LeashAttachDoAfterEvent.cs => LeashDoAfterEvents.cs} (53%) diff --git a/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs b/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs deleted file mode 100644 index 5f70cfa6642..00000000000 --- a/Content.Shared/Floofstation/Leash/Events/LeashDetachDoAfterEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Shared.Serialization; - -namespace Content.Shared.Floofstation.Leash.Events; - -[Serializable, NetSerializable] -public sealed partial class LeashDetachDoAfterEvent : SimpleDoAfterEvent -{ -} diff --git a/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs b/Content.Shared/Floofstation/Leash/LeashDoAfterEvents.cs similarity index 53% rename from Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs rename to Content.Shared/Floofstation/Leash/LeashDoAfterEvents.cs index 7ef567366d2..2b27055aa90 100644 --- a/Content.Shared/Floofstation/Leash/Events/LeashAttachDoAfterEvent.cs +++ b/Content.Shared/Floofstation/Leash/LeashDoAfterEvents.cs @@ -1,9 +1,14 @@ using Content.Shared.DoAfter; using Robust.Shared.Serialization; -namespace Content.Shared.Floofstation.Leash.Events; +namespace Content.Shared.Floofstation.Leash; [Serializable, NetSerializable] public sealed partial class LeashAttachDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public sealed partial class LeashDetachDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index a771204d606..15bdb0a16d6 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -2,9 +2,7 @@ using Content.Shared.Clothing.Components; using Content.Shared.DoAfter; using Content.Shared.Floofstation.Leash.Components; -using Content.Shared.Floofstation.Leash.Events; using Content.Shared.Hands.Components; -using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; using Content.Shared.Inventory.Events; using Content.Shared.Movement.Pulling.Events; From afa709b39987c4a73b177339b810d707ade0e94f Mon Sep 17 00:00:00 2001 From: fox Date: Tue, 20 Aug 2024 12:18:22 +0300 Subject: [PATCH 12/18] Add leash anchors to arctic foxes, sec dogs, and scugs --- Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml | 2 ++ Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml index dd59d74d3f0..f7c70ada16e 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml @@ -71,6 +71,7 @@ - Fox understands: - Fox + - type: LeashAnchor # Floofstation - type: entity name: security dog @@ -185,3 +186,4 @@ understands: - Dog - GalacticCommon + - type: LeashAnchor # Floofstation diff --git a/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml b/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml index 6fa6a2cfe3d..eae46ff0312 100644 --- a/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml +++ b/Resources/Prototypes/Floof/Entities/Mobs/NPCs/scugcat.yml @@ -104,6 +104,7 @@ understands: - ScugSign - Cat + - type: LeashAnchor # Floofstation - type: palette id: ScugCatColors From 984a93cd5974f3fc0f2795c0a24b84ab3bc2c0c4 Mon Sep 17 00:00:00 2001 From: fox Date: Wed, 21 Aug 2024 05:49:56 +0300 Subject: [PATCH 13/18] Minor changes --- .../Leash/Components/LeashComponent.cs | 2 +- .../Floofstation/Leash/LeashSystem.cs | 47 ++++++++++++------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs index 01922720ceb..20ba744c0a4 100644 --- a/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs +++ b/Content.Shared/Floofstation/Leash/Components/LeashComponent.cs @@ -74,7 +74,7 @@ public sealed partial class LeashComponent : Component public List Leashed = new(); [DataDefinition, Serializable, NetSerializable] - public partial class LeashData + public sealed partial class LeashData { [DataField] public string JointId = string.Empty; diff --git a/Content.Shared/Floofstation/Leash/LeashSystem.cs b/Content.Shared/Floofstation/Leash/LeashSystem.cs index 15bdb0a16d6..fcd77a5994a 100644 --- a/Content.Shared/Floofstation/Leash/LeashSystem.cs +++ b/Content.Shared/Floofstation/Leash/LeashSystem.cs @@ -16,6 +16,8 @@ using Robust.Shared.Map; 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.Player; using Robust.Shared.Timing; @@ -60,9 +62,9 @@ public override void Shutdown() public override void Update(float frameTime) { - var leashQuery = EntityQueryEnumerator(); + var leashQuery = EntityQueryEnumerator(); - while (leashQuery.MoveNext(out var leashEnt, out var leash)) + while (leashQuery.MoveNext(out var leashEnt, out var leash, out var physics)) { var sourceXForm = Transform(leashEnt); @@ -133,10 +135,11 @@ private void OnJointRemoved(Entity ent, ref JointRemovedEvent ar { var id = args.Joint.ID; if (!ent.Comp.Leashed.TryFirstOrDefault(it => it.JointId == id, out var data) - || !TryComp(GetEntity(data.Pulled), out var leashed)) + || !TryGetEntity(data.Pulled, out var leashedEnt) + || !TryComp(leashedEnt, out var leashed)) return; - RemoveLeash((leashed.Owner, leashed), ent!, false); + RemoveLeash((leashedEnt.Value, leashed), ent!, false); } private void OnGetEquipmentVerbs(Entity ent, ref GetVerbsEvent args) @@ -197,7 +200,7 @@ private void OnAttachDoAfter(Entity ent, ref LeashAttachDo || !CanLeash(ent, (args.Used.Value, leash))) return; - DoLeash(ent, (args.Used.Value, leash), args.Target!.Value); + DoLeash(ent, (args.Used.Value, leash), EntityUid.Invalid); } private void OnDetachDoAfter(Entity ent, ref LeashDetachDoAfterEvent args) @@ -205,7 +208,7 @@ private void OnDetachDoAfter(Entity ent, ref LeashDetachDoAfte if (args.Cancelled || args.Handled || ent.Comp.Puller is not { } leash) return; - RemoveLeash(ent!, leash, true); + RemoveLeash(ent!, leash); } private bool OnRequestPullLeash(ICommonSession? session, EntityCoordinates targetCoords, EntityUid uid) @@ -267,6 +270,19 @@ private bool TryGetLeashTarget(Entity ent, out EntityUid return true; } + private DistanceJoint CreateLeashJoint(string jointId, Entity leash, EntityUid leashTarget) + { + var joint = _joints.CreateDistanceJoint(leash, leashTarget, id: jointId); + joint.CollideConnected = false; + joint.Length = leash.Comp.Length; + joint.MinLength = 0f; + joint.MaxLength = leash.Comp.Length; + joint.Stiffness = 1f; + joint.CollideConnected = true; // This is just for performance reasons and doesn't actually make mobs collide. + + return joint; + } + #endregion #region public api @@ -281,7 +297,7 @@ public bool CanLeash(Entity anchor, Entity && !_xform.IsParentOf(Transform(leashTarget), leash); // google recursion - this makes the game explode for some reason } - public bool TryLeash(Entity anchor, Entity leash, EntityUid user) + public bool TryLeash(Entity anchor, Entity leash, EntityUid user, bool popup = true) { if (!CanLeash(anchor, leash) || !TryGetLeashTarget(anchor!, out var leashTarget)) return false; @@ -304,7 +320,7 @@ public bool TryLeash(Entity anchor, Entity }; var result = _doAfters.TryStartDoAfter(doAfter); - if (result && _net.IsServer) + if (result && _net.IsServer && popup) { (string, object)[] locArgs = [("user", user), ("target", leashTarget), ("anchor", anchor.Owner), ("selfAnchor", anchor.Owner == leashTarget)]; @@ -345,6 +361,12 @@ public bool TryUnleash(Entity leashed, Entity + /// Immediately creates the leash joint between the specified entities and sets up respective components. + /// + /// The anchor entity, usually either target's clothing or the target itself. + /// The leash entity. + /// The entity to which the leash is actually connected. Can be EntityUid.Invalid, then it will be deduced. public void DoLeash(Entity anchor, Entity leash, EntityUid leashTarget) { if (_net.IsClient || leashTarget is { Valid: false } && !TryGetLeashTarget(anchor!, out leashTarget)) @@ -357,14 +379,7 @@ public void DoLeash(Entity anchor, Entity 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 = _joints.CreateDistanceJoint(leash, leashTarget, id: leashedComp.JointId); - joint.CollideConnected = false; - joint.Length = leash.Comp.Length; - joint.MinLength = 0f; - joint.MaxLength = leash.Comp.Length; - joint.Stiffness = 0f; - joint.Damping = 0f; - + var joint = CreateLeashJoint(leashedComp.JointId, leash, leashTarget); var data = new LeashComponent.LeashData(leashedComp.JointId, netLeashTarget) { NextDamage = _timing.CurTime + leash.Comp.DamageInterval From 19d0d62c7c8b0f3d1e2f274bf5251c044d456f7a Mon Sep 17 00:00:00 2001 From: Pierson Arnold Date: Wed, 21 Aug 2024 12:54:03 -0500 Subject: [PATCH 14/18] Added vagina trait and some organization --- .../Components/SquirtProducerComponent.cs | 38 +++++ .../FloofStation/Traits/LewdTraitSystem.cs | 161 +++++++++--------- .../Traits/Events/SquirtingDoAfterEvent.cs | 10 ++ .../en-US/Floof/reagents/natural_sauce.ftl | 5 + .../floofstation/{cum => verbs}/cum-verb.ftl | 0 .../{milk => verbs}/milk-verb.ftl | 0 .../en-US/floofstation/verbs/squirt-verb.ftl | 5 + .../reagents/floofstation/natural_sauce.ftl | 2 - .../Floof/Reagents/natural_sauce.yml | 20 +++ Resources/Prototypes/Floof/Traits/lewd.yml | 39 +++-- 10 files changed, 182 insertions(+), 98 deletions(-) create mode 100644 Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs create mode 100644 Content.Shared/Floofstation/Traits/Events/SquirtingDoAfterEvent.cs create mode 100644 Resources/Locale/en-US/Floof/reagents/natural_sauce.ftl rename Resources/Locale/en-US/floofstation/{cum => verbs}/cum-verb.ftl (100%) rename Resources/Locale/en-US/floofstation/{milk => verbs}/milk-verb.ftl (100%) create mode 100644 Resources/Locale/en-US/floofstation/verbs/squirt-verb.ftl delete mode 100644 Resources/Locale/en-US/reagents/floofstation/natural_sauce.ftl diff --git a/Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs b/Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs new file mode 100644 index 00000000000..6ad056e5490 --- /dev/null +++ b/Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared.FixedPoint; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FloofStation.Traits; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.GameStates; + +namespace Content.Server.FloofStation.Traits; + +[RegisterComponent, Access(typeof(LewdTraitSystem))] +public sealed partial class SquirtProducerComponent : Component +{ + [DataField("solutionname")] + public string SolutionName = "vagina"; + + [DataField] + public ProtoId ReagentId = "Natural Lubricant"; + + [DataField] + public FixedPoint2 MaxVolume = FixedPoint2.New(25); + + [DataField] + public Entity? Solution = null; + + [DataField] + public FixedPoint2 QuantityPerUpdate = 5; + + [DataField] + public float HungerUsage = 10f; + + [DataField] + public TimeSpan GrowthDelay = TimeSpan.FromSeconds(10); + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextGrowth = TimeSpan.FromSeconds(0); +} diff --git a/Content.Server/FloofStation/Traits/LewdTraitSystem.cs b/Content.Server/FloofStation/Traits/LewdTraitSystem.cs index 6a5a927d417..d2f21480ca2 100644 --- a/Content.Server/FloofStation/Traits/LewdTraitSystem.cs +++ b/Content.Server/FloofStation/Traits/LewdTraitSystem.cs @@ -31,17 +31,17 @@ public override void Initialize() //Initializers SubscribeLocalEvent(OnComponentInitCum); SubscribeLocalEvent(OnComponentInitMilk); - //SubscribeLocalEvent(OnComponentInitSquirt); //Unused-Trait is WIP + SubscribeLocalEvent(OnComponentInitSquirt); //Verbs SubscribeLocalEvent>(AddCumVerb); SubscribeLocalEvent>(AddMilkVerb); - //SubscribeLocalEvent>(AddSquirtVerb); //Unused-Trait is WIP + SubscribeLocalEvent>(AddSquirtVerb); //Events SubscribeLocalEvent(OnDoAfterCum); SubscribeLocalEvent(OnDoAfterMilk); - //SubscribeLocalEvent(OnDoAfterSquirt); //Unused-Trait is WIP + SubscribeLocalEvent(OnDoAfterSquirt); } #region event handling @@ -61,13 +61,13 @@ private void OnComponentInitMilk(Entity entity, ref Compo solutionMilk.AddReagent(entity.Comp.ReagentId, entity.Comp.MaxVolume - solutionMilk.Volume); } - //private void OnComponentInitSquirt(Entity entity, ref ComponentStartup args) //Unused-Trait is WIP - //{ - // var solutionSquirt = _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionName); - // solutionSquirt.MaxVolume = entity.Comp.MaxVolume; + private void OnComponentInitSquirt(Entity entity, ref ComponentStartup args) + { + var solutionSquirt = _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionName); + solutionSquirt.MaxVolume = entity.Comp.MaxVolume; - // solutionSquirt.AddReagent(entity.Comp.ReagentId, entity.Comp.MaxVolume - solutionSquirt.Volume); - //} + solutionSquirt.AddReagent(entity.Comp.ReagentId, entity.Comp.MaxVolume - solutionSquirt.Volume); + } public void AddCumVerb(Entity entity, ref GetVerbsEvent args) { @@ -113,26 +113,26 @@ public void AddMilkVerb(Entity entity, ref GetVerbsEvent< args.Verbs.Add(verbMilk); } - //public void AddSquirtVerb(Entity entity, ref GetVerbsEvent args) //Unused-Trait is WIP - //{ - // if (args.Using == null || - // !args.CanInteract || - // !EntityManager.HasComponent(args.Using.Value)) //see if removing this part lets you milk on the ground. - // return; + public void AddSquirtVerb(Entity entity, ref GetVerbsEvent args) + { + if (args.Using == null || + !args.CanInteract || + !EntityManager.HasComponent(args.Using.Value)) //see if removing this part lets you milk on the ground. + return; - // _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionName); + _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionName); - // var user = args.User; - // var used = args.Using.Value; + var user = args.User; + var used = args.Using.Value; - // InnateVerb verbSquirt = new() - // { - // Act = () => AttemptSquirt(entity, user, used), - // Text = Loc.GetString($"squirt-verb-get-text"), - // Priority = 1 - // }; - // args.Verbs.Add(verbSquirt); - //} + InnateVerb verbSquirt = new() + { + Act = () => AttemptSquirt(entity, user, used), + Text = Loc.GetString($"squirt-verb-get-text"), + Priority = 1 + }; + args.Verbs.Add(verbSquirt); + } private void OnDoAfterCum(Entity entity, ref CummingDoAfterEvent args) { @@ -188,32 +188,32 @@ private void OnDoAfterMilk(Entity entity, ref MilkingDoAf _popupSystem.PopupEntity(Loc.GetString("milk-verb-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner, args.Args.User, PopupType.Medium); } - //private void OnDoAfterSquirt(Entity entity, ref SquirtingDoAfterEvent args) //Unused-Trait is WIP - //{ - // if (args.Cancelled || args.Handled || args.Args.Used == null) - // return; + private void OnDoAfterSquirt(Entity entity, ref SquirtingDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Args.Used == null) + return; - // if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution)) - // return; + if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution)) + return; - // if (!_solutionContainer.TryGetRefillableSolution(args.Args.Used.Value, out var targetSoln, out var targetSolution)) - // return; + if (!_solutionContainer.TryGetRefillableSolution(args.Args.Used.Value, out var targetSoln, out var targetSolution)) + return; - // args.Handled = true; - // var quantity = solution.Volume; - // if (quantity == 0) - // { - // _popupSystem.PopupEntity(Loc.GetString("squirt-verb-dry"), entity.Owner, args.Args.User); - // return; - // } + args.Handled = true; + var quantity = solution.Volume; + if (quantity == 0) + { + _popupSystem.PopupEntity(Loc.GetString("squirt-verb-dry"), entity.Owner, args.Args.User); + return; + } - // if (quantity > targetSolution.AvailableVolume) - // quantity = targetSolution.AvailableVolume; + if (quantity > targetSolution.AvailableVolume) + quantity = targetSolution.AvailableVolume; - // var split = _solutionContainer.SplitSolution(entity.Comp.Solution.Value, quantity); - // _solutionContainer.TryAddSolution(targetSoln.Value, split); - // _popupSystem.PopupEntity(Loc.GetString("squirt-verb-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner, args.Args.User, PopupType.Medium); - //} + var split = _solutionContainer.SplitSolution(entity.Comp.Solution.Value, quantity); + _solutionContainer.TryAddSolution(targetSoln.Value, split); + _popupSystem.PopupEntity(Loc.GetString("squirt-verb-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner, args.Args.User, PopupType.Medium); + } #endregion #region utilities @@ -249,27 +249,28 @@ private void AttemptMilk(Entity lewd, EntityUid userUid, _doAfterSystem.TryStartDoAfter(doargs); } - //private void AttemptSquirt(Entity lewd, EntityUid userUid, EntityUid containerUid) //Unused-Trait is WIP - //{ - // if (!HasComp(userUid)) - // return; + private void AttemptSquirt(Entity lewd, EntityUid userUid, EntityUid containerUid) + { + if (!HasComp(userUid)) + return; - // var doargs = new DoAfterArgs(EntityManager, userUid, 5, new SquirtingDoAfterEvent(), lewd, lewd, used: containerUid) - // { - // BreakOnUserMove = true, - // BreakOnDamage = true, - // BreakOnTargetMove = true, - // MovementThreshold = 1.0f, - // }; + var doargs = new DoAfterArgs(EntityManager, userUid, 5, new SquirtingDoAfterEvent(), lewd, lewd, used: containerUid) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + MovementThreshold = 1.0f, + }; - // _doAfterSystem.TryStartDoAfter(doargs); - //} + _doAfterSystem.TryStartDoAfter(doargs); + } public override void Update(float frameTime) { base.Update(frameTime); - var queryCum = EntityQueryEnumerator(); //SquirtProducerComponent -unused , + var queryCum = EntityQueryEnumerator(); var queryMilk = EntityQueryEnumerator(); + var querySquirt = EntityQueryEnumerator(); var now = _timing.CurTime; while (queryCum.MoveNext(out var uid, out var containerCum)) @@ -320,21 +321,29 @@ public override void Update(float frameTime) _solutionContainer.TryAddReagent(containerMilk.Solution.Value, containerMilk.ReagentId, containerMilk.QuantityPerUpdate, out _); } - //if (!(now < containerSquirt.NextGrowth)) //Unused-Trait is WIP - //{ - // containerSquirt.NextGrowth = now + containerSquirt.GrowthDelay; - - // - // if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger)) - // { - // - // if (!(_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay)) - // _hunger.ModifyHunger(uid, -containerSquirt.HungerUsage, hunger); - // } - - // if (_solutionContainer.ResolveSolution(uid, containerSquirt.SolutionName, ref containerSquirt.Solution)) - // _solutionContainer.TryAddReagent(containerSquirt.Solution.Value, containerSquirt.ReagentId, containerSquirt.QuantityPerUpdate, out _); - //} + while (querySquirt.MoveNext(out var uid, out var containerSquirt)) + { + if (now < containerSquirt.NextGrowth) + continue; + + containerSquirt.NextGrowth = now + containerSquirt.GrowthDelay; + + if (_mobState.IsDead(uid)) + continue; + + if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger)) + { + if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay) + continue; + + //_hunger.ModifyHunger(uid, -containerMilk.HungerUsage, hunger); + } + + if (!_solutionContainer.ResolveSolution(uid, containerSquirt.SolutionName, ref containerSquirt.Solution)) + continue; + + _solutionContainer.TryAddReagent(containerSquirt.Solution.Value, containerSquirt.ReagentId, containerSquirt.QuantityPerUpdate, out _); + } } #endregion } diff --git a/Content.Shared/Floofstation/Traits/Events/SquirtingDoAfterEvent.cs b/Content.Shared/Floofstation/Traits/Events/SquirtingDoAfterEvent.cs new file mode 100644 index 00000000000..2deb48b76af --- /dev/null +++ b/Content.Shared/Floofstation/Traits/Events/SquirtingDoAfterEvent.cs @@ -0,0 +1,10 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.FloofStation.Traits.Events; + +[Serializable, NetSerializable] +public sealed partial class SquirtingDoAfterEvent : SimpleDoAfterEvent +{ +} + diff --git a/Resources/Locale/en-US/Floof/reagents/natural_sauce.ftl b/Resources/Locale/en-US/Floof/reagents/natural_sauce.ftl new file mode 100644 index 00000000000..797b6bcc2f8 --- /dev/null +++ b/Resources/Locale/en-US/Floof/reagents/natural_sauce.ftl @@ -0,0 +1,5 @@ +reagent-name-cum = cum +reagent-desc-cum = A sticky cloudy-white liquid. + +reagent-name-nat-lube = natural lubricant +reagent-desc-nat-lube = A slippery clear liquid. diff --git a/Resources/Locale/en-US/floofstation/cum/cum-verb.ftl b/Resources/Locale/en-US/floofstation/verbs/cum-verb.ftl similarity index 100% rename from Resources/Locale/en-US/floofstation/cum/cum-verb.ftl rename to Resources/Locale/en-US/floofstation/verbs/cum-verb.ftl diff --git a/Resources/Locale/en-US/floofstation/milk/milk-verb.ftl b/Resources/Locale/en-US/floofstation/verbs/milk-verb.ftl similarity index 100% rename from Resources/Locale/en-US/floofstation/milk/milk-verb.ftl rename to Resources/Locale/en-US/floofstation/verbs/milk-verb.ftl diff --git a/Resources/Locale/en-US/floofstation/verbs/squirt-verb.ftl b/Resources/Locale/en-US/floofstation/verbs/squirt-verb.ftl new file mode 100644 index 00000000000..3e10d9d7d3d --- /dev/null +++ b/Resources/Locale/en-US/floofstation/verbs/squirt-verb.ftl @@ -0,0 +1,5 @@ +squirt-verb-dry = Your slit is dry. +squirt-verb-success = You fill {THE($target)} with {$amount}u of natural lubricant from your pussy. +squirt-verb-success-ground = You squirt out all over the ground! + +squirt-verb-get-text = Squirt diff --git a/Resources/Locale/en-US/reagents/floofstation/natural_sauce.ftl b/Resources/Locale/en-US/reagents/floofstation/natural_sauce.ftl deleted file mode 100644 index 887f9d8ba59..00000000000 --- a/Resources/Locale/en-US/reagents/floofstation/natural_sauce.ftl +++ /dev/null @@ -1,2 +0,0 @@ -reagent-name-cum = cum -reagent-desc-cum = A sticky cloudy-white liquid. diff --git a/Resources/Prototypes/Floof/Reagents/natural_sauce.yml b/Resources/Prototypes/Floof/Reagents/natural_sauce.yml index fe089b2ac80..403e21c102a 100644 --- a/Resources/Prototypes/Floof/Reagents/natural_sauce.yml +++ b/Resources/Prototypes/Floof/Reagents/natural_sauce.yml @@ -19,3 +19,23 @@ collection: FootstepSticky params: volume: 6 + +- type: reagent + id: Natural Lubricant + name: reagent-name-nat-lube + group: NaturalSauce + desc: reagent-desc-nat-lube + physicalDesc: reagent-physical-desc-shiny + flavor: funny + color: "#d6d6d6" + viscosity: 0.35 + recognizable: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 0.3 + footstepSound: + collection: FootstepSticky + params: + volume: 4 diff --git a/Resources/Prototypes/Floof/Traits/lewd.yml b/Resources/Prototypes/Floof/Traits/lewd.yml index fb05c082657..990f8a1b53e 100644 --- a/Resources/Prototypes/Floof/Traits/lewd.yml +++ b/Resources/Prototypes/Floof/Traits/lewd.yml @@ -38,23 +38,22 @@ - ReagentId: Milk Quantity: 50 -# WIP - Needs a Reagent -# - type: trait -# id: SquirtProducer -# category: Physical -# requirements: -# - !type:CharacterJobRequirement -# inverted: true -# jobs: -# - Borg -# - MedicalBorg -# components: -# - type: SquirtProducer -# solutionname: "vagina" -# - type: SolutionContainerManager -# solutions: -# vagina: -# maxVol: 250 -# reagents: -# - ReagentId: Water -# Quantity: 30 +- type: trait + id: SquirtProducer + category: Physical + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + components: + - type: SquirtProducer + solutionname: "vagina" + - type: SolutionContainerManager + solutions: + vagina: + maxVol: 25 + reagents: + - ReagentId: Natural Lubricant + Quantity: 25 From dc2dbcac4f42409062ebc48aa3084cacc2c32f58 Mon Sep 17 00:00:00 2001 From: Pierson Arnold Date: Wed, 21 Aug 2024 13:39:23 -0500 Subject: [PATCH 15/18] Fixing a few missed details --- .../Traits/Components/SquirtProducerComponent.cs | 2 +- .../en-US/{floofstation => Floof}/verbs/cum-verb.ftl | 0 .../en-US/{floofstation => Floof}/verbs/milk-verb.ftl | 0 .../en-US/{floofstation => Floof}/verbs/squirt-verb.ftl | 0 Resources/Prototypes/Floof/Reagents/natural_sauce.yml | 9 +++++++-- Resources/Prototypes/Floof/Traits/lewd.yml | 2 +- 6 files changed, 9 insertions(+), 4 deletions(-) rename Resources/Locale/en-US/{floofstation => Floof}/verbs/cum-verb.ftl (100%) rename Resources/Locale/en-US/{floofstation => Floof}/verbs/milk-verb.ftl (100%) rename Resources/Locale/en-US/{floofstation => Floof}/verbs/squirt-verb.ftl (100%) diff --git a/Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs b/Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs index 6ad056e5490..3bde1b7d8c4 100644 --- a/Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs +++ b/Content.Server/FloofStation/Traits/Components/SquirtProducerComponent.cs @@ -16,7 +16,7 @@ public sealed partial class SquirtProducerComponent : Component public string SolutionName = "vagina"; [DataField] - public ProtoId ReagentId = "Natural Lubricant"; + public ProtoId ReagentId = "NaturalLubricant"; [DataField] public FixedPoint2 MaxVolume = FixedPoint2.New(25); diff --git a/Resources/Locale/en-US/floofstation/verbs/cum-verb.ftl b/Resources/Locale/en-US/Floof/verbs/cum-verb.ftl similarity index 100% rename from Resources/Locale/en-US/floofstation/verbs/cum-verb.ftl rename to Resources/Locale/en-US/Floof/verbs/cum-verb.ftl diff --git a/Resources/Locale/en-US/floofstation/verbs/milk-verb.ftl b/Resources/Locale/en-US/Floof/verbs/milk-verb.ftl similarity index 100% rename from Resources/Locale/en-US/floofstation/verbs/milk-verb.ftl rename to Resources/Locale/en-US/Floof/verbs/milk-verb.ftl diff --git a/Resources/Locale/en-US/floofstation/verbs/squirt-verb.ftl b/Resources/Locale/en-US/Floof/verbs/squirt-verb.ftl similarity index 100% rename from Resources/Locale/en-US/floofstation/verbs/squirt-verb.ftl rename to Resources/Locale/en-US/Floof/verbs/squirt-verb.ftl diff --git a/Resources/Prototypes/Floof/Reagents/natural_sauce.yml b/Resources/Prototypes/Floof/Reagents/natural_sauce.yml index 403e21c102a..3eed107e14f 100644 --- a/Resources/Prototypes/Floof/Reagents/natural_sauce.yml +++ b/Resources/Prototypes/Floof/Reagents/natural_sauce.yml @@ -21,14 +21,14 @@ volume: 6 - type: reagent - id: Natural Lubricant + id: NaturalLubricant name: reagent-name-nat-lube group: NaturalSauce desc: reagent-desc-nat-lube + slippery: true physicalDesc: reagent-physical-desc-shiny flavor: funny color: "#d6d6d6" - viscosity: 0.35 recognizable: true metabolisms: Drink: @@ -39,3 +39,8 @@ collection: FootstepSticky params: volume: 4 + tileReactions: + - !type:SpillTileReaction + paralyzeTime: 0.5 + launchForwardsMultiplier: 1.2 + requiredSlipSpeed: 1 diff --git a/Resources/Prototypes/Floof/Traits/lewd.yml b/Resources/Prototypes/Floof/Traits/lewd.yml index 990f8a1b53e..cdcb36ad9a0 100644 --- a/Resources/Prototypes/Floof/Traits/lewd.yml +++ b/Resources/Prototypes/Floof/Traits/lewd.yml @@ -55,5 +55,5 @@ vagina: maxVol: 25 reagents: - - ReagentId: Natural Lubricant + - ReagentId: NaturalLubricant Quantity: 25 From 1c7fc4824ca9908bebdab75bc0cfe569dc492597 Mon Sep 17 00:00:00 2001 From: FloofStation Changelogs <175611579+Floof-Station-Bot@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:07:44 +0000 Subject: [PATCH 16/18] Automatic Changelog Update (#120) --- Resources/Changelog/Floof.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Resources/Changelog/Floof.yml b/Resources/Changelog/Floof.yml index cf0c7168b09..fa353528b8f 100644 --- a/Resources/Changelog/Floof.yml +++ b/Resources/Changelog/Floof.yml @@ -612,3 +612,13 @@ Entries: message: 'Fixed defense values for arm-warmers ' id: 84 time: '2024-08-22T10:43:09.0000000+00:00' +- author: Mnemotechnician + changes: + - type: Add + message: >- + A new leash item has been added to autolathe to help you keep your + animals and station pets in check. If the station pet in question has + hands and can speak, the leash can be attached to their collar via strip + menu. + id: 85 + time: '2024-08-23T00:07:19.0000000+00:00' From f6dc16c3242a2bb2d352945bb2e2873e45fa41a5 Mon Sep 17 00:00:00 2001 From: FloofStation Changelogs <175611579+Floof-Station-Bot@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:09:50 +0000 Subject: [PATCH 17/18] Automatic Changelog Update (#130) --- Resources/Changelog/Floof.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Resources/Changelog/Floof.yml b/Resources/Changelog/Floof.yml index fa353528b8f..2198dabdcf4 100644 --- a/Resources/Changelog/Floof.yml +++ b/Resources/Changelog/Floof.yml @@ -622,3 +622,9 @@ Entries: menu. id: 85 time: '2024-08-23T00:07:19.0000000+00:00' +- author: Memeji + changes: + - type: Add + message: '"Pussy" trait that creates a "Natural Lubricant"' + id: 86 + time: '2024-08-23T00:07:53.0000000+00:00' From fa46395a6965a474602b1ab87a3776d94514171f Mon Sep 17 00:00:00 2001 From: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com> Date: Fri, 23 Aug 2024 03:16:48 +0200 Subject: [PATCH 18/18] Update supermatter.ftl fixing quickly .ftl files --- Resources/Locale/en-US/supermatter/supermatter.ftl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Locale/en-US/supermatter/supermatter.ftl b/Resources/Locale/en-US/supermatter/supermatter.ftl index 52593f5524e..2f36560a26c 100644 --- a/Resources/Locale/en-US/supermatter/supermatter.ftl +++ b/Resources/Locale/en-US/supermatter/supermatter.ftl @@ -1,19 +1,19 @@ supermatter-announcer = Automatic Supermatter Engine supermatter-examine-integrity = Its' integrity is [color=yellow]{$integrity}%[/color]. -supermatter-announcement-warning = +supermatter-warning = Warning! Crystal hyperstructure integrity faltering! Integrity: {$integrity}%. -supermatter-announcement-emergency = +supermatter-emergency = DANGER! Crystal hyperstructure integrity reaching critical levels! Integrity: {$integrity}%. -supermatter-announcement-delam-explosion = +supermatter-delam-explosion = CRYSTAL DELAMINATION IMMINENT! The crystal has reached critical integrity failure! Emergency causality destabilization field has been engaged. -supermatter-announcement-delam-overmass = +supermatter-delam-overmass = CRYSTAL DELAMINATION IMMINENT! Crystal hyperstructure integrity has reached critical mass failure! Singularity formation imminent! -supermatter-announcement-delam-tesla = +supermatter-delam-tesla = CRYSTAL DELAMINATION IMMINENT! Crystal hyperstructure integrity has reached critical power surge failure! Energy ball formation imminent! -supermatter-announcement-delam-cascade = +supermatter-delam-cascade = CRYSTAL DELAMINATION IMMINENT! Harmonic frequency limits exceeded, casualty destabilization field could not be engaged! -supermatter-announcement-delam-cancel = +supermatter-delam-cancel = Crystalline hyperstructure returning to safe operating parameters. Failsafe has been Disengaged. Integrity: {$integrity}%. supermatter-seconds-before-delam = Estimated time before delamination: {$seconds} seconds.