From ef590762011cde21978bc10876a98b47b370b396 Mon Sep 17 00:00:00 2001 From: dng-cher Date: Fri, 7 Aug 2026 22:19:40 +0500 Subject: [PATCH 01/10] collar-and-leash-one --- .../_Fish/Leash/Components/LeashComponent.cs | 38 + .../_Fish/Leash/Systems/LeashSystem.cs | 730 ++++++++++++++++++ Content.Shared/_Fish/Leash/CollarEvents.cs | 12 + .../_Fish/Leash/Components/CollarComponent.cs | 17 + .../Leash/Components/CollarWearerComponent.cs | 7 + .../Leash/Components/LeashHolderComponent.cs | 7 + .../_Fish/Leash/Systems/SharedCollarSystem.cs | 41 + .../_fish/entities/fun/alerts/leash.ftl | 13 + .../_fish/entities/fun/collars.ftl | 2 + .../_prototypes/_fish/entities/fun/leash.ftl | 2 + Resources/Prototypes/_Fish/Alerts/alerts.yml | 8 + .../VendingMachines/petomat_catalog.yml | 15 + .../_Fish/Entities/Clothing/Neck/collars.yml | 36 + .../_Fish/Entities/Objects/Fun/leash.yml | 110 +++ .../Structures/Machines/vending_machines.yml | 35 + .../Neck/Misc/collar.rsi/equipped-NECK.png | Bin 0 -> 317 bytes .../Clothing/Neck/Misc/collar.rsi/icon.png | Bin 0 -> 656 bytes .../Clothing/Neck/Misc/collar.rsi/meta.json | 18 + .../_Fish/Objects/Fun/leash.rsi/icon.png | Bin 0 -> 1107 bytes .../Objects/Fun/leash.rsi/inhand-left.png | Bin 0 -> 662 bytes .../Objects/Fun/leash.rsi/inhand-right.png | Bin 0 -> 650 bytes .../Objects/Fun/leash.rsi/leash-icon.png | Bin 0 -> 424 bytes .../_Fish/Objects/Fun/leash.rsi/leash.png | Bin 0 -> 164 bytes .../_Fish/Objects/Fun/leash.rsi/meta.json | 28 + .../_Fish/Objects/Fun/leash_line.rsi/line.png | Bin 0 -> 131 bytes .../Objects/Fun/leash_line.rsi/meta.json | 14 + .../Machines/petomat.rsi/broken.png | Bin 0 -> 887 bytes .../Machines/petomat.rsi/deny-unshaded.png | Bin 0 -> 436 bytes .../Structures/Machines/petomat.rsi/meta.json | 44 ++ .../Machines/petomat.rsi/normal-unshaded.png | Bin 0 -> 959 bytes .../Structures/Machines/petomat.rsi/off.png | Bin 0 -> 610 bytes .../Structures/Machines/petomat.rsi/panel.png | Bin 0 -> 255 bytes 32 files changed, 1177 insertions(+) create mode 100644 Content.Server/_Fish/Leash/Components/LeashComponent.cs create mode 100644 Content.Server/_Fish/Leash/Systems/LeashSystem.cs create mode 100644 Content.Shared/_Fish/Leash/CollarEvents.cs create mode 100644 Content.Shared/_Fish/Leash/Components/CollarComponent.cs create mode 100644 Content.Shared/_Fish/Leash/Components/CollarWearerComponent.cs create mode 100644 Content.Shared/_Fish/Leash/Components/LeashHolderComponent.cs create mode 100644 Content.Shared/_Fish/Leash/Systems/SharedCollarSystem.cs create mode 100644 Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl create mode 100644 Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl create mode 100644 Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl create mode 100644 Resources/Prototypes/_Fish/Alerts/alerts.yml create mode 100644 Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml create mode 100644 Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml create mode 100644 Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml create mode 100644 Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml create mode 100644 Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png create mode 100644 Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/icon.png create mode 100644 Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/meta.json create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash.rsi/icon.png create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash.rsi/inhand-left.png create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash.rsi/inhand-right.png create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash.rsi/leash-icon.png create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash.rsi/leash.png create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash.rsi/meta.json create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash_line.rsi/line.png create mode 100644 Resources/Textures/_Fish/Objects/Fun/leash_line.rsi/meta.json create mode 100644 Resources/Textures/_Fish/Structures/Machines/petomat.rsi/broken.png create mode 100644 Resources/Textures/_Fish/Structures/Machines/petomat.rsi/deny-unshaded.png create mode 100644 Resources/Textures/_Fish/Structures/Machines/petomat.rsi/meta.json create mode 100644 Resources/Textures/_Fish/Structures/Machines/petomat.rsi/normal-unshaded.png create mode 100644 Resources/Textures/_Fish/Structures/Machines/petomat.rsi/off.png create mode 100644 Resources/Textures/_Fish/Structures/Machines/petomat.rsi/panel.png diff --git a/Content.Server/_Fish/Leash/Components/LeashComponent.cs b/Content.Server/_Fish/Leash/Components/LeashComponent.cs new file mode 100644 index 00000000000..79635d251ef --- /dev/null +++ b/Content.Server/_Fish/Leash/Components/LeashComponent.cs @@ -0,0 +1,38 @@ +using Content.Shared.Damage; + +namespace Content.Server._Fish.Leash.Components; + +[RegisterComponent, Access(typeof(Systems.LeashSystem))] +public sealed partial class LeashComponent : Component +{ + [DataField] + public DamageSpecifier ChokeDamage = new() + { + DamageDict = + { + ["Asphyxiation"] = 1, + }, + }; + + [DataField] + public TimeSpan ChokeCooldown = TimeSpan.FromSeconds(0.5); + + [DataField] + public float ChokeDistance = 3.25f; + + [DataField] + public float ResistanceThreshold = 0.05f; + + [DataField] + public float MaxTensionDistance = 1.35f; + + [DataField] + public float MaximumDistance = 4f; + + [DataField] + public float PullForce = 32f; + + public EntityUid? AttachedCollar; + public EntityUid? Holder; + public TimeSpan NextChokeTime; +} diff --git a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs new file mode 100644 index 00000000000..122c2610f09 --- /dev/null +++ b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs @@ -0,0 +1,730 @@ +using System.Numerics; +using Content.Server._Fish.Leash.Components; +using Content.Shared.Alert; +using Content.Shared._Sunrise.Movement.Carrying; +using Content.Shared._Sunrise.Movement.Carrying.Slowdown; +using Content.Shared._Sunrise.Movement.Pulling; +using Content.Shared._Fish.Leash; +using Content.Shared._Fish.Leash.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.Disposal.Components; +using Content.Shared.Disposal.Unit; +using Content.Shared.Disposal.Unit.Events; +using Content.Shared.DoAfter; +using Content.Shared.Hands; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Inventory; +using Content.Shared.Inventory.Events; +using Content.Shared.Item; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Content.Shared.Movement.Systems; +using Content.Shared.Physics; +using Content.Shared.Popups; +using Robust.Shared.Containers; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Server._Fish.Leash.Systems; + +public sealed partial class LeashSystem : EntitySystem +{ + private static readonly SpriteSpecifier.Rsi LeashVisualSprite = + new(new ResPath("/Textures/_Fish/Objects/Fun/leash_line.rsi"), "line"); + private static readonly Vector2 LeashHolderOffset = Vector2.Zero; + private static readonly Vector2 LeashWearerOffset = new(0f, 0.08f); + + [Dependency] private AlertsSystem _alerts = default!; + [Dependency] private DamageableSystem _damageable = default!; + [Dependency] private SharedDoAfterSystem _doAfter = default!; + [Dependency] private SharedHandsSystem _hands = default!; + [Dependency] private InventorySystem _inventory = default!; + [Dependency] private SharedPhysicsSystem _physics = default!; + [Dependency] private SharedPopupSystem _popup = default!; + [Dependency] private SharedTransformSystem _transform = default!; + [Dependency] private IGameTiming _timing = default!; + + private readonly HashSet _allowedCollarUnequips = new(); + // Словарь для хранения инициатора снятия ошейника (ключ — ошейник, значение — инициатор) + private readonly Dictionary _unequipInitiators = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCollarEquipped); + SubscribeLocalEvent(OnCollarUnequipAttempt); + SubscribeLocalEvent(OnCollarUnequipped); + SubscribeLocalEvent(OnCollarTerminating); + SubscribeLocalEvent(OnLeashAfterInteract); + SubscribeLocalEvent(OnLeashEquippedHand); + SubscribeLocalEvent(OnLeashUnequippedHand); + SubscribeLocalEvent(OnLeashDropped); + SubscribeLocalEvent(OnLeashInsertedIntoContainer); + SubscribeLocalEvent(OnLeashTerminating); + + SubscribeLocalEvent(OnWearerMoveInput); + SubscribeLocalEvent(OnCollarWearerPickupAttempt); + SubscribeLocalEvent(OnRemoveCollarAlert); + SubscribeLocalEvent(OnRemoveCollarDoAfter); + SubscribeLocalEvent(OnHolderMoveInput); + SubscribeLocalEvent>(OnCarryDoAfterAttempt); + SubscribeLocalEvent>(OnCarryDoAfterAttempt); + SubscribeLocalEvent(OnBeforeDisposalFlush); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var leashUid, out var leash)) + { + UpdateLeashConstraint(leashUid, leash, frameTime); + } + } + + private void OnCollarEquipped(EntityUid uid, CollarComponent component, GotEquippedEvent args) + { + if (args.Slot != "neck") + return; + + // Для версии 274 используем EquipTarget + component.Wearer = args.EquipTarget; + var wearer = EnsureComp(args.EquipTarget); + wearer.Collar = uid; + _alerts.ShowAlert(args.EquipTarget, component.Alert); + } + + private void OnCollarUnequipAttempt(EntityUid uid, CollarComponent component, BeingUnequippedAttemptEvent args) + { + if (args.Slot != "neck") + return; + + // В версии 274 в этом событии есть поле User — инициатор + var initiator = args.User; + _unequipInitiators[uid] = initiator; // Запоминаем, кто пытается снять + + // Разрешаем снятие всем, можно добавить проверку дистанции при желании + // Если хотите, чтобы только владелец мог снимать — раскомментируйте: + // if (initiator != component.Wearer) + // { + // args.Cancel(); + // args.Reason = "leash-remove-collar-blocked"; + // _popup.PopupEntity(Loc.GetString("leash-remove-collar-blocked"), initiator, initiator, PopupType.SmallCaution); + // } + } + + private void OnCollarUnequipped(EntityUid uid, CollarComponent component, GotUnequippedEvent args) + { + // В версии 274 в GotUnequippedEvent есть EquipTarget (владелец) + var owner = args.EquipTarget; + + // Получаем инициатора из словаря + _unequipInitiators.TryGetValue(uid, out var initiator); + _unequipInitiators.Remove(uid); + + // Отвязываем поводок + DetachLeashFromCollar(uid, component); + + if (TryComp(owner, out var wearer) && wearer.Collar == uid) + { + RemCompDeferred(owner); + } + + component.Wearer = null; + _alerts.ClearAlert(owner, component.Alert); + + // Если инициатор известен и это не владелец, передаём ошейник ему + if (initiator != null && initiator != owner) + { + // Ошейник уже снят и находится в инвентаре владельца (предположительно). + // Пытаемся переместить его в руки инициатору. + // Сначала проверяем, не держит ли его кто-то. + if (TryComp(uid, out var item)) + { + // Попробуем взять ошейник у владельца и передать инициатору + // Для этого используем PickupOrDrop с указанием инициатора как получателя + if (!_hands.TryPickupAnyHand(initiator.Value, uid)) + { + // Если не вышло положить в руки, роняем на пол + Transform(uid).AttachToGridOrMap(); + _popup.PopupEntity(Loc.GetString("leash-remove-collar-dropped"), uid, uid); + } + else + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-transferred"), uid, uid); + } + } + } + else + { + // Если инициатор не известен или это владелец — ошейник остаётся у владельца + _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), owner, owner); + } + } + + private void OnCollarTerminating(EntityUid uid, CollarComponent component, ref EntityTerminatingEvent args) + { + DetachLeashFromCollar(uid, component); + + if (component.Wearer is { Valid: true } wearer) + _alerts.ClearAlert(wearer, component.Alert); + } + + private void OnLeashAfterInteract(EntityUid uid, LeashComponent component, AfterInteractEvent args) + { + if (args.Handled || !args.CanReach || args.Target == null) + return; + + if (!TryResolveCollarTarget(args.Target.Value, out var collarUid, out var collar)) + { + _popup.PopupEntity(Loc.GetString("leash-no-collar"), args.User, args.User, PopupType.SmallCaution); + return; + } + + if (collarUid == component.AttachedCollar) + { + DetachLeash(uid, component, popupUser: args.User); + args.Handled = true; + return; + } + + if (collar.Wearer == null) + { + _popup.PopupEntity(Loc.GetString("leash-collar-must-be-worn"), args.User, args.User, PopupType.SmallCaution); + return; + } + + AttachLeash(uid, component, collarUid, collar, args.User); + args.Handled = true; + } + + private void OnLeashEquippedHand(EntityUid uid, LeashComponent component, GotEquippedHandEvent args) + { + component.Holder = args.User; + var holder = EnsureComp(args.User); + holder.Leashes.Add(uid); + + UpdateLeashVisuals(uid, component); + TryStartLeashPull(uid, component); + } + + // Перекладывание в другую руку НЕ отцепляет + private void OnLeashUnequippedHand(EntityUid uid, LeashComponent component, GotUnequippedHandEvent args) + { + if (component.Holder != args.User) + return; + + RemoveHolderLeash(uid, args.User); + component.Holder = null; + } + + private void OnLeashDropped(EntityUid uid, LeashComponent component, DroppedEvent args) + { + HandleLeashReleased(uid, component, args.User); + } + + private void OnLeashInsertedIntoContainer(EntityUid uid, LeashComponent component, ref EntInsertedIntoContainerMessage args) + { + if (component.AttachedCollar != null) + DetachLeash(uid, component); + + if (component.Holder != null) + { + StopLeashPull(uid, component); + RemoveHolderLeash(uid, component.Holder); + component.Holder = null; + } + + UpdateLeashVisuals(uid, component); + } + + private void OnLeashTerminating(EntityUid uid, LeashComponent component, ref EntityTerminatingEvent args) + { + DetachLeash(uid, component); + RemoveHolderLeash(uid, component.Holder); + } + + private void OnWearerMoveInput(EntityUid uid, CollarWearerComponent component, ref MoveInputEvent args) + { + if (!args.HasDirectionalMovement || + !TryGetLeash(uid, component, out _, out var leash) || + leash.Holder is not { Valid: true } holder) + { + return; + } + + if (TryStopStretchingMovement(uid, holder, leash, args.Entity.Comp)) + return; + + if (_timing.CurTime < leash.NextChokeTime) + return; + + var wearerCoords = _transform.GetMapCoordinates(uid); + var holderCoords = _transform.GetMapCoordinates(holder); + + if (wearerCoords.MapId != holderCoords.MapId) + return; + + var toHolder = holderCoords.Position - wearerCoords.Position; + if (toHolder.LengthSquared() < leash.ChokeDistance * leash.ChokeDistance) + return; + + var moveVector = GetMoveVector(args.Entity.Comp.HeldMoveButtons); + if (moveVector.LengthSquared() <= 0f) + return; + + var awayDot = Vector2.Dot(Vector2.Normalize(moveVector), Vector2.Normalize(toHolder)); + if (awayDot > -0.2f) + return; + } + + private void OnHolderMoveInput(EntityUid uid, LeashHolderComponent component, ref MoveInputEvent args) + { + foreach (var leashUid in component.Leashes) + { + if (!TryComp(leashUid, out var leash) || + !TryGetLeashWearer(leash, out var wearer) || + !TryStopStretchingMovement(uid, wearer, leash, args.Entity.Comp)) + { + continue; + } + + return; + } + } + + private void OnCollarWearerPickupAttempt(EntityUid uid, CollarWearerComponent component, GettingPickedUpAttemptEvent args) + { + if (!HasActiveLeash(uid)) + return; + + args.Cancel(); + if (args.ShowPopup) + _popup.PopupEntity(Loc.GetString("leash-cannot-pick-up"), uid, args.User, PopupType.SmallCaution); + } + + private void OnCarryDoAfterAttempt(EntityUid uid, TComponent component, DoAfterAttemptEvent args) + where TComponent : Component + { + if (!HasActiveLeash(uid) && !HasActiveLeash(args.DoAfter.Args.User)) + return; + + args.Cancel(); + _popup.PopupEntity(Loc.GetString("leash-cannot-carry"), uid, args.DoAfter.Args.User, PopupType.SmallCaution); + } + + private void OnBeforeDisposalFlush(EntityUid uid, DisposalUnitComponent component, BeforeDisposalFlushEvent args) + { + foreach (var contained in component.Container.ContainedEntities) + { + if (!HasActiveLeash(contained)) + continue; + + args.Cancel(); + return; + } + } + + private void OnRemoveCollarAlert(EntityUid uid, CollarWearerComponent component, ref RemoveCollarAlertEvent args) + { + if (args.Handled || + component.Collar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer != uid) + { + return; + } + + if (!TryStartRemoveCollarDoAfter(uid, uid, collarUid, collar)) + return; + + args.Handled = true; + } + + private void OnRemoveCollarDoAfter(EntityUid uid, CollarWearerComponent component, RemoveCollarDoAfterEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + + if (component.Collar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer != uid) + { + return; + } + + if (args.Cancelled) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); + return; + } + + EntityUid? removedItem; + _allowedCollarUnequips.Add(collarUid); + try + { + if (!_inventory.TryUnequip(uid, uid, "neck", out removedItem, checkDoafter: false)) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); + return; + } + } + finally + { + _allowedCollarUnequips.Remove(collarUid); + } + + if (removedItem != null) + _hands.PickupOrDrop(uid, removedItem.Value); + + _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), uid, uid); + } + + private void HandleLeashReleased(EntityUid uid, LeashComponent component, EntityUid user) + { + if (component.Holder != user) + return; + + if (component.AttachedCollar != null) + DetachLeash(uid, component); + else + UpdateLeashVisuals(uid, component); + + StopLeashPull(uid, component); + RemoveHolderLeash(uid, component.Holder); + component.Holder = null; + } + + private void AttachLeash(EntityUid leashUid, LeashComponent leash, EntityUid collarUid, CollarComponent collar, EntityUid user) + { + DetachLeash(leashUid, leash); + + if (collar.AttachedLeash is { Valid: true } otherLeashUid && + otherLeashUid != leashUid && + TryComp(otherLeashUid, out var otherLeash)) + { + DetachLeash(otherLeashUid, otherLeash); + } + + leash.AttachedCollar = collarUid; + collar.AttachedLeash = leashUid; + leash.NextChokeTime = _timing.CurTime; + + if (collar.Wearer is { Valid: true } wearer) + { + _popup.PopupEntity(Loc.GetString("leash-attached-user"), user, user); + _popup.PopupEntity(Loc.GetString("leash-attached-target"), wearer, wearer); + } + + UpdateLeashVisuals(leashUid, leash); + TryStartLeashPull(leashUid, leash); + } + + private void DetachLeash(EntityUid leashUid, LeashComponent leash, EntityUid? popupUser = null) + { + var oldCollarUid = leash.AttachedCollar; + if (oldCollarUid == null) + return; + + StopLeashPull(leashUid, leash); + + if (TryComp(oldCollarUid, out var oldCollar) && + oldCollar.AttachedLeash == leashUid) + { + oldCollar.AttachedLeash = null; + + if (popupUser != null && + oldCollar.Wearer is { Valid: true } wearer) + { + _popup.PopupEntity(Loc.GetString("leash-detached-user"), popupUser.Value, popupUser.Value); + _popup.PopupEntity(Loc.GetString("leash-detached-target"), wearer, wearer); + } + } + + leash.AttachedCollar = null; + UpdateLeashVisuals(leashUid, leash); + } + + private void DetachLeashFromCollar(EntityUid collarUid, CollarComponent collar) + { + if (collar.AttachedLeash is not { Valid: true } leashUid || + !TryComp(leashUid, out var leash)) + { + collar.AttachedLeash = null; + return; + } + + if (leash.AttachedCollar == collarUid) + DetachLeash(leashUid, leash); + + collar.AttachedLeash = null; + } + + private void TryStartLeashPull(EntityUid leashUid, LeashComponent leash) + { + if (leash.Holder is not { Valid: true } holder || + leash.AttachedCollar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer is not { Valid: true } wearer || + holder == wearer) + { + return; + } + } + + private void StopLeashPull(EntityUid leashUid, LeashComponent leash) + { + if (leash.Holder is { Valid: true } holder) + _physics.WakeBody(holder); + + if (leash.AttachedCollar is { Valid: true } collarUid && + TryComp(collarUid, out var collar) && + collar.Wearer is { Valid: true } wearer) + { + _physics.WakeBody(wearer); + } + } + + private void RemoveHolderLeash(EntityUid leashUid, EntityUid? holderUid) + { + if (holderUid is not { Valid: true } holder || + !TryComp(holder, out var holderComp)) + { + return; + } + + holderComp.Leashes.Remove(leashUid); + if (holderComp.Leashes.Count == 0) + RemCompDeferred(holder); + } + + private bool TryResolveCollarTarget(EntityUid target, out EntityUid collarUid, out CollarComponent collar) + { + collarUid = default; + collar = default!; + + if (TryComp(target, out CollarComponent? directCollar) && + directCollar.Wearer != null) + { + collar = directCollar; + collarUid = target; + return true; + } + + if (!_inventory.TryGetSlotEntity(target, "neck", out EntityUid? neckItem) || + neckItem == null || + !TryComp(neckItem.Value, out CollarComponent? slotCollar)) + { + return false; + } + + collar = slotCollar; + collarUid = neckItem.Value; + return true; + } + + private bool TryGetLeash(EntityUid wearer, CollarWearerComponent wearerComp, out EntityUid leashUid, out LeashComponent leash) + { + leashUid = default; + leash = default!; + + if (wearerComp.Collar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer != wearer || + collar.AttachedLeash is not { Valid: true } attachedLeash || + !TryComp(attachedLeash, out LeashComponent? leashComp)) + { + return false; + } + + leash = leashComp; + leashUid = attachedLeash; + return true; + } + + private bool TryGetLeashWearer(LeashComponent leash, out EntityUid wearer) + { + wearer = default; + + if (leash.AttachedCollar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer is not { Valid: true } collarWearer) + { + return false; + } + + wearer = collarWearer; + return true; + } + + private bool HasActiveLeash(EntityUid uid) + { + if (TryComp(uid, out var wearer) && + wearer.Collar is { Valid: true } collarUid && + TryComp(collarUid, out var collar) && + collar.AttachedLeash is { Valid: true }) + { + return true; + } + + if (!TryComp(uid, out var holder)) + return false; + + foreach (var leashUid in holder.Leashes) + { + if (TryComp(leashUid, out var leash) && + leash.Holder == uid && + leash.AttachedCollar is { Valid: true }) + { + return true; + } + } + + return false; + } + + private void UpdateLeashVisuals(EntityUid leashUid, LeashComponent leash) + { + if (leash.Holder is { Valid: true } holder && + leash.AttachedCollar is { Valid: true } collarUid && + TryComp(collarUid, out var collar) && + collar.Wearer is { Valid: true } wearer && + holder != wearer) + { + var visuals = EnsureComp(leashUid); + visuals.Sprite = LeashVisualSprite; + visuals.Target = wearer; + visuals.OffsetA = LeashHolderOffset; + visuals.OffsetB = LeashWearerOffset; + Dirty(leashUid, visuals); + return; + } + + RemCompDeferred(leashUid); + } + + private static Vector2 GetMoveVector(MoveButtons buttons) + { + var x = 0; + x -= (buttons & MoveButtons.Left) != 0 ? 1 : 0; + x += (buttons & MoveButtons.Right) != 0 ? 1 : 0; + + var y = 0; + y -= (buttons & MoveButtons.Down) != 0 ? 1 : 0; + y += (buttons & MoveButtons.Up) != 0 ? 1 : 0; + + var vector = new Vector2(x, y); + return vector.LengthSquared() > 0f ? Vector2.Normalize(vector) : Vector2.Zero; + } + + private bool TryStartRemoveCollarDoAfter(EntityUid user, EntityUid wearerUid, EntityUid collarUid, CollarComponent collar) + { + var doAfter = new DoAfterArgs(EntityManager, user, collar.BreakoutTime, new RemoveCollarDoAfterEvent(), wearerUid, target: wearerUid, used: collarUid) + { + BreakOnMove = true, + BreakOnDamage = true, + NeedHand = true, + BreakOnDropItem = false, + }; + + if (!_doAfter.TryStartDoAfter(doAfter)) + return false; + + _popup.PopupEntity(Loc.GetString("leash-remove-collar-start"), wearerUid, wearerUid); + return true; + } + + private bool TryStopStretchingMovement(EntityUid mover, EntityUid anchor, LeashComponent leash, InputMoverComponent moverComp) + { + if (!IsMovingAwayPastLimit(mover, anchor, moverComp.HeldMoveButtons, leash.MaximumDistance)) + return false; + + moverComp.CurTickWalkMovement = Vector2.Zero; + moverComp.CurTickSprintMovement = Vector2.Zero; + Dirty(mover, moverComp); + _physics.WakeBody(mover); + return true; + } + + private bool IsMovingAwayPastLimit(EntityUid mover, EntityUid anchor, MoveButtons buttons, float maxDistance) + { + var moverCoords = _transform.GetMapCoordinates(mover); + var anchorCoords = _transform.GetMapCoordinates(anchor); + + if (moverCoords.MapId != anchorCoords.MapId) + return false; + + var away = moverCoords.Position - anchorCoords.Position; + if (away.LengthSquared() < maxDistance * maxDistance) + return false; + + var moveVector = GetMoveVector(buttons); + if (moveVector.LengthSquared() <= 0f || away.LengthSquared() <= 0.001f) + return false; + + return Vector2.Dot(Vector2.Normalize(moveVector), Vector2.Normalize(away)) > 0.2f; + } + + private void UpdateLeashConstraint(EntityUid leashUid, LeashComponent leash, float frameTime) + { + if (leash.Holder is not { Valid: true } holder || + leash.AttachedCollar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer is not { Valid: true } wearer || + holder == wearer) + { + return; + } + + var wearerCoords = _transform.GetMapCoordinates(wearer); + var holderCoords = _transform.GetMapCoordinates(holder); + if (wearerCoords.MapId != holderCoords.MapId) + { + DetachLeash(leashUid, leash); + return; + } + + var delta = holderCoords.Position - wearerCoords.Position; + var distance = delta.Length(); + if (distance <= leash.MaxTensionDistance || distance <= 0.001f) + return; + + if (!TryComp(wearer, out var wearerBody)) + return; + + var direction = delta / distance; + var excess = distance - leash.MaxTensionDistance; + var impulse = direction * (leash.PullForce * MathF.Max(1f, excess * 3f) * wearerBody.Mass * frameTime); + + _physics.WakeBody(wearer); + _physics.ApplyLinearImpulse(wearer, impulse, body: wearerBody); + + if (distance >= leash.MaximumDistance && + TryComp(holder, out var holderBody)) + { + var holderImpulse = -direction * (leash.PullForce * MathF.Max(1f, (distance - leash.MaximumDistance) * 3f) * holderBody.Mass * frameTime); + _physics.WakeBody(holder); + _physics.ApplyLinearImpulse(holder, holderImpulse, body: holderBody); + + if (TryComp(holder, out var holderMover)) + TryStopStretchingMovement(holder, wearer, leash, holderMover); + } + + if (distance < leash.ChokeDistance || _timing.CurTime < leash.NextChokeTime) + return; + + leash.NextChokeTime = _timing.CurTime + leash.ChokeCooldown; + _damageable.TryChangeDamage(wearer, leash.ChokeDamage, ignoreResistances: true, interruptsDoAfters: false, origin: holder); + _popup.PopupEntity(Loc.GetString("leash-choking"), wearer, wearer, PopupType.SmallCaution); + } +} \ No newline at end of file diff --git a/Content.Shared/_Fish/Leash/CollarEvents.cs b/Content.Shared/_Fish/Leash/CollarEvents.cs new file mode 100644 index 00000000000..14120cc8c65 --- /dev/null +++ b/Content.Shared/_Fish/Leash/CollarEvents.cs @@ -0,0 +1,12 @@ +using Content.Shared.Alert; +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._Fish.Leash; + +public sealed partial class RemoveCollarAlertEvent : BaseAlertEvent; + +[Serializable, NetSerializable] +public sealed partial class RemoveCollarDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/_Fish/Leash/Components/CollarComponent.cs b/Content.Shared/_Fish/Leash/Components/CollarComponent.cs new file mode 100644 index 00000000000..178452cd28e --- /dev/null +++ b/Content.Shared/_Fish/Leash/Components/CollarComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Alert; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Fish.Leash.Components; + +[RegisterComponent] +public sealed partial class CollarComponent : Component +{ + [DataField] + public TimeSpan BreakoutTime = TimeSpan.FromSeconds(4); + + [DataField] + public ProtoId Alert = "Collared"; + + public EntityUid? Wearer; + public EntityUid? AttachedLeash; +} diff --git a/Content.Shared/_Fish/Leash/Components/CollarWearerComponent.cs b/Content.Shared/_Fish/Leash/Components/CollarWearerComponent.cs new file mode 100644 index 00000000000..74ea5470c6c --- /dev/null +++ b/Content.Shared/_Fish/Leash/Components/CollarWearerComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared._Fish.Leash.Components; + +[RegisterComponent] +public sealed partial class CollarWearerComponent : Component +{ + public EntityUid? Collar; +} diff --git a/Content.Shared/_Fish/Leash/Components/LeashHolderComponent.cs b/Content.Shared/_Fish/Leash/Components/LeashHolderComponent.cs new file mode 100644 index 00000000000..4718279bb2a --- /dev/null +++ b/Content.Shared/_Fish/Leash/Components/LeashHolderComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared._Fish.Leash.Components; + +[RegisterComponent] +public sealed partial class LeashHolderComponent : Component +{ + public readonly HashSet Leashes = new(); +} diff --git a/Content.Shared/_Fish/Leash/Systems/SharedCollarSystem.cs b/Content.Shared/_Fish/Leash/Systems/SharedCollarSystem.cs new file mode 100644 index 00000000000..7475b7412dc --- /dev/null +++ b/Content.Shared/_Fish/Leash/Systems/SharedCollarSystem.cs @@ -0,0 +1,41 @@ +using Content.Shared._Fish.Leash.Components; +using Content.Shared.Inventory; +using Content.Shared.Interaction; +using Content.Shared.Nutrition.EntitySystems; + +namespace Content.Shared._Fish.Leash.Systems; + +public sealed partial class SharedCollarSystem : EntitySystem +{ + [Dependency] private InventorySystem _inventory = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCollarAfterInteract, before: [typeof(IngestionSystem)]); + } + + private void OnCollarAfterInteract(EntityUid uid, CollarComponent component, ref AfterInteractEvent args) + { + if (args.Handled || + !args.CanReach || + args.Target is not { Valid: true } target || + !TryComp(target, out var inventory)) + { + return; + } + + _inventory.TryEquip( + args.User, + target, + uid, + "neck", + predicted: true, + inventory: inventory, + checkDoafter: true, + triggerHandContact: false); + + args.Handled = true; + } +} diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl new file mode 100644 index 00000000000..2ec9635a69c --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl @@ -0,0 +1,13 @@ +leash-no-collar = На цели нет ошейника. +leash-collar-must-be-worn = Поводок можно пристегнуть только к надетому ошейнику. +leash-attached-user = Вы пристёгиваете поводок к ошейнику. +leash-attached-target = К вашему ошейнику пристегнули поводок. +leash-detached-user = Вы отстёгиваете поводок. +leash-detached-target = Поводок отстегнули от вашего ошейника. +leash-choking = Ошейник впивается в шею и душит вас! +leash-remove-collar-start = Вы начинаете возиться с застёжкой ошейника. +leash-remove-collar-fail = Вам не удаётся снять ошейник. +leash-remove-collar-success = Вам удаётся снять ошейник. +leash-remove-collar-blocked = Сначала нужно расстегнуть ошейник. +leash-cannot-pick-up = Поводок натянулся. +leash-cannot-carry = Поводок натянулся. diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl new file mode 100644 index 00000000000..0c89fb24c4a --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl @@ -0,0 +1,2 @@ +ent-LeashBase = серый поводок + .desc = Поводок-рулетка, который можно прикрепить к ошейнику. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl new file mode 100644 index 00000000000..a973b01480e --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl @@ -0,0 +1,2 @@ +ent-ClothingNeckCollarBase = серый ошейник + .desc = Ошейник, с кольцом для поводка. Зачем он вам? \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Alerts/alerts.yml b/Resources/Prototypes/_Fish/Alerts/alerts.yml new file mode 100644 index 00000000000..093c9de73b3 --- /dev/null +++ b/Resources/Prototypes/_Fish/Alerts/alerts.yml @@ -0,0 +1,8 @@ +- type: alert + id: Collared + clickEvent: !type:RemoveCollarAlertEvent + icons: + - sprite: /Textures/_Fish/Clothing/Neck/Misc/petcollar.rsi + state: icon + name: alerts-collared-name + description: alerts-collared-desc \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml b/Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml new file mode 100644 index 00000000000..faa3a15a3cc --- /dev/null +++ b/Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml @@ -0,0 +1,15 @@ +- type: vendingMachineInventory + id: PetMateVendInventory + startingInventory: + # Collars + ClothingNeckCollarBase: 2 + ClothingNeckCollarRed: 2 + ClothingNeckCollarBlue: 2 + ClothingNeckCollarPink: 2 + ClothingNeckCollarGreen: 2 + # Leashs + LeashBase: 2 + LeashRed: 2 + LeashBlue: 2 + LeashPink: 2 + LeashGreen: 2 \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml new file mode 100644 index 00000000000..00a0729f8cd --- /dev/null +++ b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml @@ -0,0 +1,36 @@ +- type: entity + parent: ClothingNeckBase + id: ClothingNeckCollarBase + name: grey collar + description: A collar with a leash ring. Why do you need it? + components: + - type: Item + size: Small + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + color: "#DCDCDC" + - type: Butcherable + butcheringType: Knife + spawned: + - id: MaterialCloth1 + amount: 1 + - type: Clothing + quickEquip: true + equipSound: /Audio/Item/belt_equip.ogg + unequipSound: /Audio/Item/belt_equip.ogg + equipDelay: 2 + unequipDelay: 1 + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + clothingVisuals: + collar: + - state: equipped-NECK + color: "#DCDCDC" + - type: Sprite + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + scale: 0.75, 0.75 + layers: + - state: icon + color: "#DCDCDC" + - type: Collar + - type: Tag + tags: + - Recyclable diff --git a/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml new file mode 100644 index 00000000000..9b5e315468c --- /dev/null +++ b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml @@ -0,0 +1,110 @@ +# Base +- type: entity + parent: BaseItem + id: LeashBase + name: grey leash + description: A leash roulette that attaches to the collar. + components: + - type: Sprite + sprite: _Fish/Objects/Fun/leash.rsi + scale: 0.75, 0.75 + layers: + - state: icon + color: "#DCDCDC" + - state: leash-icon + - type: Item + sprite: _Fish/Objects/Fun/leash.rsi + size: Normal + inhandVisuals: + left: + - state: inhand-left + color: "#DCDCDC" + right: + - state: inhand-right + color: "#DCDCDC" + - type: Leash + +# Colored +- type: entity + parent: LeashBase + id: LeashRed + name: red leash + components: + - type: Sprite + sprite: _Fish/Objects/Fun/leash.rsi + layers: + - state: icon + color: "#B32200" + - state: leash-icon + - type: Item + sprite: _Fish/Objects/Fun/leash.rsi + inhandVisuals: + left: + - state: inhand-left + color: "#B32200" + right: + - state: inhand-right + color: "#B32200" + +- type: entity + parent: LeashBase + id: LeashBlue + name: blue leash + components: + - type: Sprite + sprite: _Fish/Objects/Fun/leash.rsi + layers: + - state: icon + color: "#0060B3" + - state: leash-icon + - type: Item + sprite: _Fish/Objects/Fun/leash.rsi + inhandVisuals: + left: + - state: inhand-left + color: "#0060B3" + right: + - state: inhand-right + color: "#0060B3" + +- type: entity + parent: LeashBase + id: LeashPink + name: pink leash + components: + - type: Sprite + sprite: _Fish/Objects/Fun/leash.rsi + layers: + - state: icon + color: "#FF7AF0" + - state: leash-icon + - type: Item + sprite: _Fish/Objects/Fun/leash.rsi + inhandVisuals: + left: + - state: inhand-left + color: "#FF7AF0" + right: + - state: inhand-right + color: "#FF7AF0" + +- type: entity + parent: LeashBase + id: LeashGreen + name: green leash + components: + - type: Sprite + sprite: _Fish/Objects/Fun/leash.rsi + layers: + - state: icon + color: "#009118" + - state: leash-icon + - type: Item + sprite: _Fish/Objects/Fun/leash.rsi + inhandVisuals: + left: + - state: inhand-left + color: "#009118" + right: + - state: inhand-right + color: "#009118" \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml new file mode 100644 index 00000000000..073c1b6cf72 --- /dev/null +++ b/Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml @@ -0,0 +1,35 @@ +- type: entity + parent: VendingMachine + id: VendingMachinePet + name: PetMate + description: Bring your pet to work! + components: + - type: VendingMachine + pack: PetMateVendInventory + dispenseOnHitChance: 0.25 + dispenseOnHitThreshold: 2 + offState: off + brokenState: broken + normalState: normal-unshaded + screenState: screen + ejectDelay: 5 + soundVend: /Audio/Machines/machine_vend_hot_drink.ogg + initialStockQuality: 0.33 + - type: Sprite + sprite: _Fish/Structures/Machines/VendingMachines/petmate.rsi + layers: + - state: "off" + map: ["enum.VendingMachineVisualLayers.Base"] + - state: "off" + map: ["enum.VendingMachineVisualLayers.BaseUnshaded"] + shader: unshaded + - state: "screen" + map: ["enum.VendingMachineVisualLayers.Screen"] + shader: unshaded + - state: panel + map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: PointLight + radius: 1.5 + energy: 2.0 + color: "#ffddaa" + - type: PlayerCountDependentStock \ No newline at end of file diff --git a/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png b/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png new file mode 100644 index 0000000000000000000000000000000000000000..14809d6e74a90ffc42d2983ac9a1c18c085c2c1d GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!AqVljv*CsZ*QH=Z7~pNd*~@Dv!Wqs#evv@ z1s(4l#6uhmgAdBANcwh!E5g-tkD~pp?-O+ns^>i3S#^ei;lS!DYj*jGQyyE`ovGIU zpC>r+v(5Zu?WGa7w@rVa|8Cx-qWPX)<@PDzT^%<4pUvloh8mWiPmO%{zV?3f_0rJn zztMsdC(T=B2XqG%JkX!~<%05u+4FwRUuFIGtyj&gWzP3ZdSmaZDrsIXwM{u}Fk@zD zX-7@vp?hH+wWm+{izHO*Eu9f6Cj&9yK=v-i6T6e7{zV1>y~@Dg>FVdQ&MBb@0RD=4 AZ2$lO literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/icon.png b/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..34a6c2f7f48b2195abdf2964c5e9a569e3104ad8 GIT binary patch literal 656 zcmV;B0&o3^P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0wqaAK~z{r?UXS~ z>rfbmpI$B%I@qbJU^j6PmwU*OQC6m-axbc%y( z7ULk;<+${OoRhRxuW*+})nPn=~L zVHg77x^8K{3ez+N!13|1vQJeAr6`)GX-X7D@C{=f8RYQrkWQyl8RC!pVOf@7b>-6B zb=}5K%~=I^UAGZM5owy{w;{bK1}GxUb^WKv5LGAvMxzmq;}oS|#c>==(^Rz54Fo}u zzjVJz7={Evpv&a|0KV^I7)Jh_{uRS8gzx*h4Ek4KS(Z3GJ>}@=h>wpC=JPoK7Z(>l zdLKN`696YCC+zI(Fr7|$dV1pa_BP+|ud+fz{uu#+Zv>IXa{Xel&_~u|S(doJzZXf8 zh&YZ#5zq6Kx(a*O`T$xO?+iq qWa~kRZQC2y42Q$5Le|vONIn7KKQ^?~Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1L#RaK~z{r?UqeQ zV^3qO*LE;P|dMsSkXg?YE{P)yQNrQms~N_5zUo#G1%3tFvK z}E+*pH#klk))Wo4y#I=j2O0EEL~079Wqm#V67 zM@B|IS5>tP)G=1TD4s|p#5<)SJ~}o5z~;spu~`DT*TLLg8?j zP$-0|s*I11_w@JomuQzjbLn+)t#-rKZ@&C2z3uIZw(zxVw z#FH^Izz_iS zn`*QBPGng&J&OcVsgwlp>h~=nJoh{S!13|12+*nDT!Ys5ya$S+NEV9)Niz30Qw0)< zghWGl?s&<2qMfnt{PfkuUI5=qXb=-tn?WWn( z7K?0aHkoMjfa`EL%<%9qJ3Bijf5!yw!Xn#1x(zR%9-(RfG*3lQG<}4_VbKM%*(?Aj zCnw^@>UiON|HF?+R=MRhU^oRj<O)qG!0*y@vAj_Yy zwDh9o4!95R4A8G&zu!+bnA)R{#>4}s|B?5tf>8QQ0@JIeP Z{0%;^)a`9#6K4Pb002ovPDHLkV1jZ%44eP} literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Fish/Objects/Fun/leash.rsi/inhand-left.png b/Resources/Textures/_Fish/Objects/Fun/leash.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..6b1425dc5b576b5305b10d0efa3cf340cee20560 GIT binary patch literal 662 zcmV;H0%`q;P)s(ni8NOxcqOS;qDyLSrYlH_@=VwSU*gGcNFfG{9( z1f&EaCxBdnQUX!}ks}}_5IF(lTR=)cN+5Csqy)GGNRmX=pV@4d-9RHPN#)wKlWkEbQahC!AhXt?aV|L_@f(jepZa;9$Vz#f^G-eQxE)!5GlWn}$jV8o_hq zq#3{ooODl`0Tcvh6aYBrg`g`82qBA{7=!^F^g_@T2857BP7J~T4tgQz3IjsOA}0o6 z00+GgbcF#SWRVktFo1(z2)e?65VFXLK^VY654!b+r`{UpG(V}^Q@5|b*U!?|=(8(6 z?+idQ6hiIs-#Wu^(j=qN$lB>a(-p09*oW?TJT7}1wl=xRer3+4_}BT0vHc62+wFFx zleWG->6y;Cy?Xfh&jRhEk%A}t`d*VQ*PM3-kfSFvx)5@$+XAH$aOs=YkyXm9$v&|F zYbO0BbL`%rUdKi4=9|q%>HiUcGfjnh|Il-FesRa7X{zS)x$#1AozDiKX>eWJAN?I5 w0CbI#$M#1UaBRf8cAhYxYZ*DVKhZPb8_a7*dm{9gNdN!<07*qoM6N<$f?`K6TmS$7 literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Fish/Objects/Fun/leash.rsi/inhand-right.png b/Resources/Textures/_Fish/Objects/Fun/leash.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..509d16e299804271f070e1336db4ed009f5239ac GIT binary patch literal 650 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9GG!XV7ZFl&wk0|S$d zr;B4q1>@Vuysx;@}Enl&;Tu%Y5i3OWCc6rJk#nc&^ufxVP!rzbPwO zR29k|S+A*Uk8DdV-tDfZW^T>x{;e!MJ$>KbH0JNBQQ4x!(cPAnPxKf#n3dceycCL< zPN-_ADyRxrG6gYB6n6N>x`bs3i;}y-FQJIA|N7sWU(G72N#ye1>n~*PT-@7{=?I6Z*0H zgNYXdAAgx&&-~}xt1rhNJ$L+j_R0-KTi%Ga?_R!Y=g#!Dha&Iqyprd%Vm#2o%XIt+ zH{*d69^D^8Pq^9`4tz~<{w&1SqIC4Cp9DFa2iB3Rm~Yj3Jg<0D`0L&0c$50Nn$(gL zk=3GW`DcDMYIv1Xw*UI;r&Wq7B_}QgOm#L}Y9^wyo8NJo*O#3k?&9GWzB2`EVZWlx z!YV4cKVa(WoRFpeKF9BLuoh{X6?++jld>O#pxLoQ~q&OF%~Zsu17Ufn&0RcwD4N(1(rTv6P+5SX|aJYD@<);T3K0RV`n B8;Jk_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Fish/Objects/Fun/leash.rsi/leash-icon.png b/Resources/Textures/_Fish/Objects/Fun/leash.rsi/leash-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..38c1a5a48d13e77d2032d91bd337e7f466b2018f GIT binary patch literal 424 zcmV;Z0ayNsP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0X<1XK~z{r?UXTV zgFqODUpu`N@heK5%+wCm0Ehwef?_b`EqUa?xCKdqrzAx*#hT}NM^IVyQ zK_IVJD@0Lb15o1`6B_`UrUB>NYMSO)2LL2Va<&9)03d{bF(wHiN(h5Mu5a%#TP%?z zN!#5tCaN>WnB<&W_%WyRIb7F86h(vGd!DDpQ40000!lvI6;>1s;*b3=DjSL74G){)!Z!prxmaV@QPi+j9$f84NfW94{S>X#Oj{P|kf; t$lcT7;vWM-OVhsnnSD-XKQmA@64IP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0}M$-K~z{r#g|V= z6j2<`+iBUhq>0;|UlZ8bJQRGj#SAXm>1fsVXMM z`tf1qQ#=981D(AE$YPQXiA1Sv;b>2TQpg8xL0}MiT;J6J>+&UD(A5F6`H$NMAlqj^ zOlMFkTYT!oSij>zZUO~R5{U$C+vaW4G$Bb6x3S-y-89O^L1eE*7GM|#;8+MX5!5E8 zGsyNCE)RqeDu5oRvMd9&1qDIic1e(m4E zB`ARc`~pW11nzDY18{5)&eH=F+qPlbHe^|bAP5iy0j6nkQOq-=Q$<%6sG5$s#Vzu* zI*|pix2;qv+zZaVXDq#%xtJ`lEQ`N2OFz>zxmTDdzjGjr$O2H?21$~5`k@vVl}d$g z!G)azEb7_f&TKGEllP1148(NCF*BUV0xav)rIIA^z+>KETLS&vUSJ#xp@ zAH@3dDzAIKsszvX89o=1s1Z$J_W^x=I{t-ZcxV6s@bJYK+8v9xPj3Q1pn!%kaI~l4 zXiq~fRYfjUUN literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/deny-unshaded.png b/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/deny-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..fa93aa5f110b8e32a4fc642f17e24d7384b5276e GIT binary patch literal 436 zcmV;l0ZaagP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0ZB^_#woA&kL8U!!Pngt{Rr1D-}uNs5k5UM{yr{S`4{xYDS_uB z4@S5x-P+{A2!ZF*M|x!|zi|No;Ds|>`jgst;fyY|!bU!;08Gb-%MCCc>(SRkFmh|t z60d-)K;$eC?^ZxoAl|JIISU{w=t?_OCbB}0zHW(EfNDFMFAvQ?vT$bK(%lF2hVW^RGCM+uq9pr z^yKZ~6lPJo&deS33`o~o{5-$aCS7moQY&oaH!dJa_i&Q~-krQQZgN19?&%}FI(NPh eLI@$mpYscRnc5i6Vm!qF0000(E{-7;ac}Q#%)1;Q!uCMeV5+Bc=PWhJ zfag6^|8Vs5ys?qBR#xs8See|$w_=e{UzC$(f~d0bgV!?g=9g@@zAt?HZ+?mEtzCaN zp4%Hbu`XB%XcHKiGXIv{T~qtl&YI)<%j>JtzqM!lc*-BEZlC$@<=oHLC%?-6Ck#|^ z=9sWo8spwGhfV>hv&V#g2TNVOUh|w$K(P0@`zPOM!iUVBEIx52G?_w+WK z?3r`LSNHz0zHWPxt)O^!b>@zpOP^M`UVN}4;`CQ9+xvGu7wug&S6{lp_A6Ui!x1Cv zYdFAeh6$m>5`JYWBza=Zz{J+AD{ z^Ye>tpEAGc`{Xp+fn5vMe)~Lm%b(>%`(C!175?AEc*n5zZQZ_?Pd+c5ng8AC^}YQ$ z4A+FUj&^zk{r1vdbg*#c^!qOM@lE!V6UyrDJ{L}5ejuE_Tyd7(8|}Xdduuko`u5rN z#$U0Wrt|$L@IGMt^4Yg;qr=PF7&+~YH|-tgkJ_FRU2zq{wI|54M#zlrh5?z3B( zyVb+C_%Zz7%lO7*h3)(YjX4Z8*FP^0p5o__c|AJz@48963im6YsZUV*^-)Rg*U#MI zf76q{eM{bE>G9I_!<~Nue?96i|FBwcu-*Jzy#K1VHSu%&9d7^qvF$S>*oO<$|1n-p Vj(+eg?|mjnz|+;wWt~$(6974`$esWI literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/off.png b/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/off.png new file mode 100644 index 0000000000000000000000000000000000000000..87ce0fe3586d64e38cb21f5726d93239de623e2d GIT binary patch literal 610 zcmV-o0-gPdP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0ryEnK~z{r?Up}C z8(|p6f0j#BsB)!)xS2p4v_qv(x|BjusR|A$6gt}Ru#m;kDWi+flCc>Jg*pT@VkD$A zLlKM}S_cbFH|bCsv|vpR?2td+y?6I1>%oPTF>xlfPGF?= zdZgwMXc%}j|AffsXk8c>sec}Sv-No*`pX9gY-ZlL8n`Bf%cg7KiW?Xhf$-4z_wZ0% zQ!TfC1&*f@U~%%s*^AfPly|oPh}^mlK>5>q(dmxtkKCE&`O`^WZvS-rpF_Z603e%A z%5orP7M1~m8$Y*jCN<~7ApI2?8`V?3QYa#lzUIzQF zJ66Be+cLMdnxgXc3%7O4X;9$X06@qi0jnNQ80gU$A1mK&JzfMd;D-xMzz^L7x(WQ> z3DnG*ltAcuG=?6H+3G5M8>pH!PRvt|e*bc;6Z4d6!$hG}q);l#+dXj%2VAD%-wq?y zH1wT7^WpNd;t50E?uq9#L`<=KCdb41DO=r(d?werR!u^#!WK9LECv9ct$dK>K)hPI w2hhF&>q|*HG`FB_1DztU0jYHYHv|8iKj?4uxwrq6X8-^I07*qoM6N<$f*GL^xc~qF literal 0 HcmV?d00001 diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/panel.png b/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/panel.png new file mode 100644 index 0000000000000000000000000000000000000000..a38f39a6832b30267700d4cb0eac14efdc5a7493 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!;1o|6$B+p3x92u;GC7L0JdD?M*uXZK*X$PG zHU{O@21U%t+y;VTEV-^X_s(r%KJw9Qjat1fy2_qmLzMpxKXa?Pm&)vXzu7 Date: Sat, 8 Aug 2026 09:14:12 +0500 Subject: [PATCH 02/10] feachi-1 --- .../_fish/entities/fun/collars.ftl | 10 ++- .../_prototypes/_fish/entities/fun/leash.ftl | 10 ++- .../structures/machines/vending_machines.ftl | 2 + .../_Fish/Entities/Clothing/Neck/collars.yml | 82 ++++++++++++++++++ .../Structures/Machines/vending_machines.yml | 23 ++--- .../{petomat.rsi => petmate.rsi}/broken.png | Bin .../deny-unshaded.png | Bin .../{petomat.rsi => petmate.rsi}/meta.json | 0 .../normal-unshaded.png | Bin .../{petomat.rsi => petmate.rsi}/off.png | Bin .../{petomat.rsi => petmate.rsi}/panel.png | Bin 11 files changed, 110 insertions(+), 17 deletions(-) create mode 100644 Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl rename Resources/Textures/_Fish/Structures/Machines/{petomat.rsi => petmate.rsi}/broken.png (100%) rename Resources/Textures/_Fish/Structures/Machines/{petomat.rsi => petmate.rsi}/deny-unshaded.png (100%) rename Resources/Textures/_Fish/Structures/Machines/{petomat.rsi => petmate.rsi}/meta.json (100%) rename Resources/Textures/_Fish/Structures/Machines/{petomat.rsi => petmate.rsi}/normal-unshaded.png (100%) rename Resources/Textures/_Fish/Structures/Machines/{petomat.rsi => petmate.rsi}/off.png (100%) rename Resources/Textures/_Fish/Structures/Machines/{petomat.rsi => petmate.rsi}/panel.png (100%) diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl index 0c89fb24c4a..49dac2f93d1 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl @@ -1,2 +1,10 @@ ent-LeashBase = серый поводок - .desc = Поводок-рулетка, который можно прикрепить к ошейнику. \ No newline at end of file + .desc = Поводок-рулетка, который можно прикрепить к ошейнику. +ent-LeashRed = красный поводок + .desc = { LeashBase } +ent-LeashBlue = синий поводок + .desc = { LeashBase } +ent-LeashPink = розовый поводок + .desc = { LeashBase } +ent-LeashGreen = зелёный поводок + .desc = { LeashBase } \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl index a973b01480e..e589faf9b34 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl @@ -1,2 +1,10 @@ ent-ClothingNeckCollarBase = серый ошейник - .desc = Ошейник, с кольцом для поводка. Зачем он вам? \ No newline at end of file + .desc = Ошейник, с кольцом для поводка. Зачем он вам? +ent-ClothingNeckCollarRed = красный ошейник + .desc = { ent-ClothingNeckCollarBase } +ent-ClothingNeckCollarBlue = синий ошейник + .desc = { ent-ClothingNeckCollarBase } +ent-ClothingNeckCollarPink = розовый ошейник + .desc = { ent-ClothingNeckCollarBase } +ent-ClothingNeckCollarGreen = зелёный ошейник + .desc = { ent-ClothingNeckCollarBase } \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl new file mode 100644 index 00000000000..1ae53048f5a --- /dev/null +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl @@ -0,0 +1,2 @@ +ent-VendingMachinePetMate = питомце-мат + .desc = Приведи питомца на работу! \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml index 00a0729f8cd..be9e5464228 100644 --- a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml +++ b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml @@ -1,3 +1,4 @@ +# Base - type: entity parent: ClothingNeckBase id: ClothingNeckCollarBase @@ -34,3 +35,84 @@ - type: Tag tags: - Recyclable + +# Colored +- type: entity + parent: ClothingNeckCollarBase + id: ClothingNeckCollarRed + name: red collar + components: + - type: Item + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + color: "#B32200" + - type: Clothing + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + clothingVisuals: + collar: + - state: equipped-NECK + color: "#B32200" + - type: Sprite + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + layers: + - state: icon + color: "#B32200" + +- type: entity + parent: ClothingNeckCollarBase + id: ClothingNeckCollarBlue + name: blue collar + components: + - type: Item + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + color: "#0060B3" + - type: Clothing + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + clothingVisuals: + collar: + - state: equipped-NECK + color: "#0060B3" + - type: Sprite + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + layers: + - state: icon + color: "#0060B3" + +- type: entity + parent: ClothingNeckCollarBase + id: ClothingNeckCollarPink + name: pink collar + components: + - type: Item + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + color: "#FF7AF0" + - type: Clothing + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + clothingVisuals: + collar: + - state: equipped-NECK + color: "#FF7AF0" + - type: Sprite + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + layers: + - state: icon + color: "#FF7AF0" + +- type: entity + parent: ClothingNeckCollarBase + id: ClothingNeckCollarGreen + name: green collar + components: + - type: Item + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + color: "#009118" + - type: Clothing + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + clothingVisuals: + collar: + - state: equipped-NECK + color: "#009118" + - type: Sprite + sprite: _Fish/Clothing/Neck/Misc/collar.rsi + layers: + - state: icon + color: "#009118" \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml index 073c1b6cf72..2b928d1d6c7 100644 --- a/Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/_Fish/Entities/Structures/Machines/vending_machines.yml @@ -1,35 +1,28 @@ - type: entity parent: VendingMachine - id: VendingMachinePet - name: PetMate + id: VendingMachinePetMate + suffix: DO NOT MAP + name: Pet-O-Mat description: Bring your pet to work! components: - type: VendingMachine pack: PetMateVendInventory - dispenseOnHitChance: 0.25 - dispenseOnHitThreshold: 2 offState: off brokenState: broken normalState: normal-unshaded - screenState: screen - ejectDelay: 5 - soundVend: /Audio/Machines/machine_vend_hot_drink.ogg - initialStockQuality: 0.33 + denyState: deny-unshaded + ejectDelay: 2 - type: Sprite - sprite: _Fish/Structures/Machines/VendingMachines/petmate.rsi + sprite: _Fish/Structures/Machines/petmate.rsi layers: - state: "off" map: ["enum.VendingMachineVisualLayers.Base"] - state: "off" map: ["enum.VendingMachineVisualLayers.BaseUnshaded"] shader: unshaded - - state: "screen" - map: ["enum.VendingMachineVisualLayers.Screen"] - shader: unshaded - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: PointLight radius: 1.5 - energy: 2.0 - color: "#ffddaa" - - type: PlayerCountDependentStock \ No newline at end of file + energy: 1.8 + color: "#ffddaa" \ No newline at end of file diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/broken.png b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/broken.png similarity index 100% rename from Resources/Textures/_Fish/Structures/Machines/petomat.rsi/broken.png rename to Resources/Textures/_Fish/Structures/Machines/petmate.rsi/broken.png diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/deny-unshaded.png b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/deny-unshaded.png similarity index 100% rename from Resources/Textures/_Fish/Structures/Machines/petomat.rsi/deny-unshaded.png rename to Resources/Textures/_Fish/Structures/Machines/petmate.rsi/deny-unshaded.png diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/meta.json b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json similarity index 100% rename from Resources/Textures/_Fish/Structures/Machines/petomat.rsi/meta.json rename to Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/normal-unshaded.png b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/normal-unshaded.png similarity index 100% rename from Resources/Textures/_Fish/Structures/Machines/petomat.rsi/normal-unshaded.png rename to Resources/Textures/_Fish/Structures/Machines/petmate.rsi/normal-unshaded.png diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/off.png b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/off.png similarity index 100% rename from Resources/Textures/_Fish/Structures/Machines/petomat.rsi/off.png rename to Resources/Textures/_Fish/Structures/Machines/petmate.rsi/off.png diff --git a/Resources/Textures/_Fish/Structures/Machines/petomat.rsi/panel.png b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/panel.png similarity index 100% rename from Resources/Textures/_Fish/Structures/Machines/petomat.rsi/panel.png rename to Resources/Textures/_Fish/Structures/Machines/petmate.rsi/panel.png From 02d272451f9fdd2279a8794f14ffbc33e1105c44 Mon Sep 17 00:00:00 2001 From: dng-cher Date: Sat, 8 Aug 2026 09:29:29 +0500 Subject: [PATCH 03/10] changelog --- Resources/Changelog/ChangelogFish.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Resources/Changelog/ChangelogFish.yml b/Resources/Changelog/ChangelogFish.yml index 5180fc36795..42547573dec 100644 --- a/Resources/Changelog/ChangelogFish.yml +++ b/Resources/Changelog/ChangelogFish.yml @@ -118,4 +118,10 @@ Entries: - message: Природная и теневая аномалии пульсируют на 60 секунд реже. type: Tweak id: 11 - time: '2026-08-05T19:40:30.000000+00:00' \ No newline at end of file + time: '2026-08-05T19:40:30.000000+00:00' +- author: dng-cher + changes: + - message: Добавлены ошейники и поводки, а так же тогромат с ними... + type: Tweak + id: 12 + time: '2026-08-08T012:00:00.000000+00:00' \ No newline at end of file From 67510e2b906445f66731ca4a0ec04660819efc48 Mon Sep 17 00:00:00 2001 From: dng-cher Date: Thu, 20 Aug 2026 20:28:37 +0500 Subject: [PATCH 04/10] fix-?? --- Resources/Changelog/ChangelogFish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Changelog/ChangelogFish.yml b/Resources/Changelog/ChangelogFish.yml index 42547573dec..46181f97294 100644 --- a/Resources/Changelog/ChangelogFish.yml +++ b/Resources/Changelog/ChangelogFish.yml @@ -123,5 +123,5 @@ Entries: changes: - message: Добавлены ошейники и поводки, а так же тогромат с ними... type: Tweak - id: 12 + id: 23 time: '2026-08-08T012:00:00.000000+00:00' \ No newline at end of file From 3cf3f07e2c0c1914b37f21b433a72cf254517233 Mon Sep 17 00:00:00 2001 From: dng-cher Date: Fri, 21 Aug 2026 07:45:58 +0500 Subject: [PATCH 05/10] FIXES-4 --- .../_Fish/Leash/Components/LeashComponent.cs | 6 + .../_Fish/Leash/Systems/LeashSystem.cs | 169 +++++++++++------- Resources/Changelog/ChangelogFish.yml | 8 +- .../_fish/entities/fun/alerts/leash.ftl | 1 + .../_fish/entities/fun/collars.ftl | 10 +- .../_prototypes/_fish/entities/fun/leash.ftl | 10 +- .../structures/machines/vending_machines.ftl | 2 +- .../_Fish/Entities/Clothing/Neck/collars.yml | 22 +-- .../_Fish/Entities/Objects/Fun/leash.yml | 18 +- .../Neck/Misc/collar.rsi/equipped-NECK.png | Bin 317 -> 250 bytes .../Structures/Machines/petmate.rsi/meta.json | 2 +- 11 files changed, 146 insertions(+), 102 deletions(-) diff --git a/Content.Server/_Fish/Leash/Components/LeashComponent.cs b/Content.Server/_Fish/Leash/Components/LeashComponent.cs index 79635d251ef..392e3be33b2 100644 --- a/Content.Server/_Fish/Leash/Components/LeashComponent.cs +++ b/Content.Server/_Fish/Leash/Components/LeashComponent.cs @@ -32,6 +32,12 @@ public sealed partial class LeashComponent : Component [DataField] public float PullForce = 32f; + [DataField] + public List Modes = new() { 1f, 2.5f, 4f, 5.5f, 7f }; + + [DataField] + public int CurrentModeIndex = 2; + public EntityUid? AttachedCollar; public EntityUid? Holder; public TimeSpan NextChokeTime; diff --git a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs index 122c2610f09..ff4e3794ba3 100644 --- a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs +++ b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs @@ -23,6 +23,7 @@ using Content.Shared.Movement.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Verbs; using Robust.Shared.Containers; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; @@ -51,8 +52,6 @@ public sealed partial class LeashSystem : EntitySystem [Dependency] private IGameTiming _timing = default!; private readonly HashSet _allowedCollarUnequips = new(); - // Словарь для хранения инициатора снятия ошейника (ключ — ошейник, значение — инициатор) - private readonly Dictionary _unequipInitiators = new(); public override void Initialize() { @@ -62,6 +61,8 @@ public override void Initialize() SubscribeLocalEvent(OnCollarUnequipAttempt); SubscribeLocalEvent(OnCollarUnequipped); SubscribeLocalEvent(OnCollarTerminating); + SubscribeLocalEvent>(OnCollarGetVerbs); + SubscribeLocalEvent(OnLeashAfterInteract); SubscribeLocalEvent(OnLeashEquippedHand); SubscribeLocalEvent(OnLeashUnequippedHand); @@ -77,6 +78,8 @@ public override void Initialize() SubscribeLocalEvent>(OnCarryDoAfterAttempt); SubscribeLocalEvent>(OnCarryDoAfterAttempt); SubscribeLocalEvent(OnBeforeDisposalFlush); + + SubscribeLocalEvent(OnRemoveCollarDoAfterVerb); } public override void Update(float frameTime) @@ -95,11 +98,10 @@ private void OnCollarEquipped(EntityUid uid, CollarComponent component, GotEquip if (args.Slot != "neck") return; - // Для версии 274 используем EquipTarget - component.Wearer = args.EquipTarget; - var wearer = EnsureComp(args.EquipTarget); + component.Wearer = args.Equipee; + var wearer = EnsureComp(args.Equipee); wearer.Collar = uid; - _alerts.ShowAlert(args.EquipTarget, component.Alert); + _alerts.ShowAlert(args.Equipee, component.Alert); } private void OnCollarUnequipAttempt(EntityUid uid, CollarComponent component, BeingUnequippedAttemptEvent args) @@ -107,30 +109,14 @@ private void OnCollarUnequipAttempt(EntityUid uid, CollarComponent component, Be if (args.Slot != "neck") return; - // В версии 274 в этом событии есть поле User — инициатор - var initiator = args.User; - _unequipInitiators[uid] = initiator; // Запоминаем, кто пытается снять - - // Разрешаем снятие всем, можно добавить проверку дистанции при желании - // Если хотите, чтобы только владелец мог снимать — раскомментируйте: - // if (initiator != component.Wearer) - // { - // args.Cancel(); - // args.Reason = "leash-remove-collar-blocked"; - // _popup.PopupEntity(Loc.GetString("leash-remove-collar-blocked"), initiator, initiator, PopupType.SmallCaution); - // } + if (_allowedCollarUnequips.Contains(uid)) + return; } private void OnCollarUnequipped(EntityUid uid, CollarComponent component, GotUnequippedEvent args) { - // В версии 274 в GotUnequippedEvent есть EquipTarget (владелец) - var owner = args.EquipTarget; - - // Получаем инициатора из словаря - _unequipInitiators.TryGetValue(uid, out var initiator); - _unequipInitiators.Remove(uid); + var owner = args.Equipee; - // Отвязываем поводок DetachLeashFromCollar(uid, component); if (TryComp(owner, out var wearer) && wearer.Collar == uid) @@ -140,34 +126,6 @@ private void OnCollarUnequipped(EntityUid uid, CollarComponent component, GotUne component.Wearer = null; _alerts.ClearAlert(owner, component.Alert); - - // Если инициатор известен и это не владелец, передаём ошейник ему - if (initiator != null && initiator != owner) - { - // Ошейник уже снят и находится в инвентаре владельца (предположительно). - // Пытаемся переместить его в руки инициатору. - // Сначала проверяем, не держит ли его кто-то. - if (TryComp(uid, out var item)) - { - // Попробуем взять ошейник у владельца и передать инициатору - // Для этого используем PickupOrDrop с указанием инициатора как получателя - if (!_hands.TryPickupAnyHand(initiator.Value, uid)) - { - // Если не вышло положить в руки, роняем на пол - Transform(uid).AttachToGridOrMap(); - _popup.PopupEntity(Loc.GetString("leash-remove-collar-dropped"), uid, uid); - } - else - { - _popup.PopupEntity(Loc.GetString("leash-remove-collar-transferred"), uid, uid); - } - } - } - else - { - // Если инициатор не известен или это владелец — ошейник остаётся у владельца - _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), owner, owner); - } } private void OnCollarTerminating(EntityUid uid, CollarComponent component, ref EntityTerminatingEvent args) @@ -178,6 +136,95 @@ private void OnCollarTerminating(EntityUid uid, CollarComponent component, ref E _alerts.ClearAlert(wearer, component.Alert); } + private void OnCollarGetVerbs(EntityUid uid, CollarComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + if (component.Wearer == null || component.Wearer == args.User) + return; + + var verb = new AlternativeVerb + { + Act = () => TryRemoveCollarWithDoAfter(args.User, uid, component), + Text = Loc.GetString("leash-remove-collar-verb"), + Priority = 2, + }; + args.Verbs.Add(verb); + } + + private void TryRemoveCollarWithDoAfter(EntityUid user, EntityUid collarUid, CollarComponent collar) + { + if (collar.Wearer == null) + return; + + var doAfter = new DoAfterArgs(EntityManager, user, collar.BreakoutTime, new RemoveCollarDoAfterEvent(), collar.Wearer.Value, target: collar.Wearer, used: collarUid) + { + BreakOnMove = true, + BreakOnDamage = true, + NeedHand = true, + BreakOnDropItem = false, + }; + + if (!_doAfter.TryStartDoAfter(doAfter)) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), user, user, PopupType.SmallCaution); + return; + } + + _popup.PopupEntity(Loc.GetString("leash-remove-collar-start"), user, user); + } + + private void OnRemoveCollarDoAfterVerb(RemoveCollarDoAfterEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + + var (user, target, used) = (args.Args.User, args.Args.Target, args.Args.Used); + if (target == null || used == null) + return; + + var collarUid = used.Value; + if (!TryComp(collarUid, out var collar) || collar.Wearer != target) + return; + + if (args.Cancelled) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), target.Value, target.Value, PopupType.SmallCaution); + return; + } + + EntityUid? removedItem; + _allowedCollarUnequips.Add(collarUid); + try + { + if (!_inventory.TryUnequip(target.Value, user, "neck", out removedItem, checkDoafter: false)) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), target.Value, target.Value, PopupType.SmallCaution); + return; + } + } + finally + { + _allowedCollarUnequips.Remove(collarUid); + } + + if (removedItem != null) + { + if (!_hands.TryPickupAnyHand(user, removedItem.Value)) + { + Transform(removedItem.Value).AttachToGridOrMap(); + _popup.PopupEntity(Loc.GetString("leash-remove-collar-dropped"), user, user); + } + else + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-transferred"), user, user); + } + } + } + private void OnLeashAfterInteract(EntityUid uid, LeashComponent component, AfterInteractEvent args) { if (args.Handled || !args.CanReach || args.Target == null) @@ -216,7 +263,6 @@ private void OnLeashEquippedHand(EntityUid uid, LeashComponent component, GotEqu TryStartLeashPull(uid, component); } - // Перекладывание в другую руку НЕ отцепляет private void OnLeashUnequippedHand(EntityUid uid, LeashComponent component, GotUnequippedHandEvent args) { if (component.Holder != args.User) @@ -224,6 +270,7 @@ private void OnLeashUnequippedHand(EntityUid uid, LeashComponent component, GotU RemoveHolderLeash(uid, args.User); component.Holder = null; + UpdateLeashVisuals(uid, component); } private void OnLeashDropped(EntityUid uid, LeashComponent component, DroppedEvent args) @@ -343,9 +390,7 @@ private void OnRemoveCollarAlert(EntityUid uid, CollarWearerComponent component, return; } - if (!TryStartRemoveCollarDoAfter(uid, uid, collarUid, collar)) - return; - + TryStartRemoveCollarDoAfter(uid, collarUid, collar); args.Handled = true; } @@ -397,12 +442,10 @@ private void HandleLeashReleased(EntityUid uid, LeashComponent component, Entity if (component.AttachedCollar != null) DetachLeash(uid, component); - else - UpdateLeashVisuals(uid, component); - StopLeashPull(uid, component); RemoveHolderLeash(uid, component.Holder); component.Holder = null; + UpdateLeashVisuals(uid, component); } private void AttachLeash(EntityUid leashUid, LeashComponent leash, EntityUid collarUid, CollarComponent collar, EntityUid user) @@ -627,9 +670,9 @@ private static Vector2 GetMoveVector(MoveButtons buttons) return vector.LengthSquared() > 0f ? Vector2.Normalize(vector) : Vector2.Zero; } - private bool TryStartRemoveCollarDoAfter(EntityUid user, EntityUid wearerUid, EntityUid collarUid, CollarComponent collar) + private bool TryStartRemoveCollarDoAfter(EntityUid user, EntityUid collarUid, CollarComponent collar) { - var doAfter = new DoAfterArgs(EntityManager, user, collar.BreakoutTime, new RemoveCollarDoAfterEvent(), wearerUid, target: wearerUid, used: collarUid) + var doAfter = new DoAfterArgs(EntityManager, user, collar.BreakoutTime, new RemoveCollarDoAfterEvent(), user, target: user, used: collarUid) { BreakOnMove = true, BreakOnDamage = true, @@ -640,7 +683,7 @@ private bool TryStartRemoveCollarDoAfter(EntityUid user, EntityUid wearerUid, En if (!_doAfter.TryStartDoAfter(doAfter)) return false; - _popup.PopupEntity(Loc.GetString("leash-remove-collar-start"), wearerUid, wearerUid); + _popup.PopupEntity(Loc.GetString("leash-remove-collar-start"), user, user); return true; } diff --git a/Resources/Changelog/ChangelogFish.yml b/Resources/Changelog/ChangelogFish.yml index 46181f97294..5180fc36795 100644 --- a/Resources/Changelog/ChangelogFish.yml +++ b/Resources/Changelog/ChangelogFish.yml @@ -118,10 +118,4 @@ Entries: - message: Природная и теневая аномалии пульсируют на 60 секунд реже. type: Tweak id: 11 - time: '2026-08-05T19:40:30.000000+00:00' -- author: dng-cher - changes: - - message: Добавлены ошейники и поводки, а так же тогромат с ними... - type: Tweak - id: 23 - time: '2026-08-08T012:00:00.000000+00:00' \ No newline at end of file + time: '2026-08-05T19:40:30.000000+00:00' \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl index 2ec9635a69c..c864d049ce2 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl @@ -11,3 +11,4 @@ leash-remove-collar-success = Вам удаётся снять ошейник. leash-remove-collar-blocked = Сначала нужно расстегнуть ошейник. leash-cannot-pick-up = Поводок натянулся. leash-cannot-carry = Поводок натянулся. +leash-mode-changed = Длина поводка установлена на { $length } метр. diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl index 49dac2f93d1..12e0c42cb92 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl @@ -1,10 +1,10 @@ ent-LeashBase = серый поводок - .desc = Поводок-рулетка, который можно прикрепить к ошейнику. + .desc = Поводок-рулетка, который можно прикрепить к ошейнику. ent-LeashRed = красный поводок - .desc = { LeashBase } + .desc = Поводок-рулетка красного цвета, который можно прикрепить к ошейнику. ent-LeashBlue = синий поводок - .desc = { LeashBase } + .desc = Поводок-рулетка синего цвета, который можно прикрепить к ошейнику. ent-LeashPink = розовый поводок - .desc = { LeashBase } + .desc = Поводок-рулетка розового цвета, который можно прикрепить к ошейнику. ent-LeashGreen = зелёный поводок - .desc = { LeashBase } \ No newline at end of file + .desc = Поводок-рулетка зелёного цвета, который можно прикрепить к ошейнику. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl index e589faf9b34..bd931ca60ab 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl @@ -1,10 +1,10 @@ ent-ClothingNeckCollarBase = серый ошейник - .desc = Ошейник, с кольцом для поводка. Зачем он вам? + .desc = Ошейник, с кольцом для поводка. Зачем он вам? ent-ClothingNeckCollarRed = красный ошейник - .desc = { ent-ClothingNeckCollarBase } + .desc = Ошейник красного цвета, с кольцом для поводка. Зачем он вам? ent-ClothingNeckCollarBlue = синий ошейник - .desc = { ent-ClothingNeckCollarBase } + .desc = Ошейник синего цвета, с кольцом для поводка. Зачем он вам? ent-ClothingNeckCollarPink = розовый ошейник - .desc = { ent-ClothingNeckCollarBase } + .desc = Ошейник розового цвета, с кольцом для поводка. Зачем он вам? ent-ClothingNeckCollarGreen = зелёный ошейник - .desc = { ent-ClothingNeckCollarBase } \ No newline at end of file + .desc = Ошейник зелёного цвета, с кольцом для поводка. Зачем он вам? \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl index 1ae53048f5a..27193e07aff 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/structures/machines/vending_machines.ftl @@ -1,2 +1,2 @@ ent-VendingMachinePetMate = питомце-мат - .desc = Приведи питомца на работу! \ No newline at end of file + .desc = Приведи питомца на работу! \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml index be9e5464228..ddc2f10eec8 100644 --- a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml +++ b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml @@ -16,8 +16,8 @@ amount: 1 - type: Clothing quickEquip: true - equipSound: /Audio/Item/belt_equip.ogg - unequipSound: /Audio/Item/belt_equip.ogg + equipSound: /Audio/Items/belt_equip.ogg + unequipSound: /Audio/Items/belt_equip.ogg equipDelay: 2 unequipDelay: 1 sprite: _Fish/Clothing/Neck/Misc/collar.rsi @@ -44,18 +44,18 @@ components: - type: Item sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#B32200" + color: "#ff6b6b" - type: Clothing sprite: _Fish/Clothing/Neck/Misc/collar.rsi clothingVisuals: collar: - state: equipped-NECK - color: "#B32200" + color: "#ff6b6b" - type: Sprite sprite: _Fish/Clothing/Neck/Misc/collar.rsi layers: - state: icon - color: "#B32200" + color: "#ff6b6b" - type: entity parent: ClothingNeckCollarBase @@ -64,18 +64,18 @@ components: - type: Item sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#0060B3" + color: "#6bcbff" - type: Clothing sprite: _Fish/Clothing/Neck/Misc/collar.rsi clothingVisuals: collar: - state: equipped-NECK - color: "#0060B3" + color: "#6bcbff" - type: Sprite sprite: _Fish/Clothing/Neck/Misc/collar.rsi layers: - state: icon - color: "#0060B3" + color: "#6bcbff" - type: entity parent: ClothingNeckCollarBase @@ -104,15 +104,15 @@ components: - type: Item sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#009118" + color: "#82ffa3" - type: Clothing sprite: _Fish/Clothing/Neck/Misc/collar.rsi clothingVisuals: collar: - state: equipped-NECK - color: "#009118" + color: "#82ffa3" - type: Sprite sprite: _Fish/Clothing/Neck/Misc/collar.rsi layers: - state: icon - color: "#009118" \ No newline at end of file + color: "#82ffa3" \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml index 9b5e315468c..2bc8f2eb474 100644 --- a/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml +++ b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml @@ -34,17 +34,17 @@ sprite: _Fish/Objects/Fun/leash.rsi layers: - state: icon - color: "#B32200" + color: "#ff6b6b" - state: leash-icon - type: Item sprite: _Fish/Objects/Fun/leash.rsi inhandVisuals: left: - state: inhand-left - color: "#B32200" + color: "#ff6b6b" right: - state: inhand-right - color: "#B32200" + color: "#ff6b6b" - type: entity parent: LeashBase @@ -55,17 +55,17 @@ sprite: _Fish/Objects/Fun/leash.rsi layers: - state: icon - color: "#0060B3" + color: "#6bcbff" - state: leash-icon - type: Item sprite: _Fish/Objects/Fun/leash.rsi inhandVisuals: left: - state: inhand-left - color: "#0060B3" + color: "#6bcbff" right: - state: inhand-right - color: "#0060B3" + color: "#6bcbff" - type: entity parent: LeashBase @@ -97,14 +97,14 @@ sprite: _Fish/Objects/Fun/leash.rsi layers: - state: icon - color: "#009118" + color: "#82ffa3" - state: leash-icon - type: Item sprite: _Fish/Objects/Fun/leash.rsi inhandVisuals: left: - state: inhand-left - color: "#009118" + color: "#82ffa3" right: - state: inhand-right - color: "#009118" \ No newline at end of file + color: "#82ffa3" \ No newline at end of file diff --git a/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png b/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png index 14809d6e74a90ffc42d2983ac9a1c18c085c2c1d..969979f5b511303c469c6a64c4b61b06e7fda321 100644 GIT binary patch delta 172 zcmdnX^owyqSbe{zi(^Pd+}m@8T+I#w4i`@fx_ommn^$@BnD84>og7yd{SBExDq)HO z&JXtHEt_F4$iVPnTaZ`vyyclyGxxu}bKWxkR)0rFRQBxWJFiBq@3;STdefgZ{0tCK z|D-H#kMi?7L0%Jsx=Le{Z*P-0{&8{ie66Kh_Pt;Hde;rr@3wu9I}#=I{xX7e{ID;7 W!D~A8tJ9X>Ag-sYpUXO@geCx7uueDt delta 239 zcmeyxxR+@{Sp6kW7srr_xVN{?=C&9Jv_13`m08h{wBkT)!GezW4&osWhQSAARwR8p z!WH4_xku6d*7u1z2i0>P@2on*z;Ix7l{LHk#3_$0?9No{|IZVg_}ONDvi8!5+uNqU z&wn>>Qqg=*uX6j8@U9M<{?F#~LqiSA&!w)W67}VvvZZtDnm{r-UW|MsI5V diff --git a/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json index 9828e4e1de5..91417a0d624 100644 --- a/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json +++ b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json @@ -27,7 +27,7 @@ 1, 0.2, 1, - 0.2, + 0.2 ] ] }, From 50bae29f1384cd4a6824c79136fa6af57a56757f Mon Sep 17 00:00:00 2001 From: dng-cher Date: Fri, 21 Aug 2026 08:15:58 +0500 Subject: [PATCH 06/10] fix-price --- .../_fish/entities/fun/collars.ftl | 10 +- .../_prototypes/_fish/entities/fun/leash.ftl | 10 +- Resources/Prototypes/_Fish/Alerts/alerts.yml | 2 +- .../_Fish/Entities/Clothing/Neck/collars.yml | 91 +---------------- .../_Fish/Entities/Objects/Fun/leash.yml | 96 ++---------------- .../Neck/Misc/collar.rsi/equipped-NECK.png | Bin 250 -> 199 bytes .../Clothing/Neck/Misc/collar.rsi/icon.png | Bin 656 -> 917 bytes 7 files changed, 14 insertions(+), 195 deletions(-) diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl index 12e0c42cb92..982db1fccb9 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/collars.ftl @@ -1,10 +1,2 @@ ent-LeashBase = серый поводок - .desc = Поводок-рулетка, который можно прикрепить к ошейнику. -ent-LeashRed = красный поводок - .desc = Поводок-рулетка красного цвета, который можно прикрепить к ошейнику. -ent-LeashBlue = синий поводок - .desc = Поводок-рулетка синего цвета, который можно прикрепить к ошейнику. -ent-LeashPink = розовый поводок - .desc = Поводок-рулетка розового цвета, который можно прикрепить к ошейнику. -ent-LeashGreen = зелёный поводок - .desc = Поводок-рулетка зелёного цвета, который можно прикрепить к ошейнику. \ No newline at end of file + .desc = Поводок-рулетка, который можно прицепить к ошейнику. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl index bd931ca60ab..e7a27baeee3 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/leash.ftl @@ -1,10 +1,2 @@ ent-ClothingNeckCollarBase = серый ошейник - .desc = Ошейник, с кольцом для поводка. Зачем он вам? -ent-ClothingNeckCollarRed = красный ошейник - .desc = Ошейник красного цвета, с кольцом для поводка. Зачем он вам? -ent-ClothingNeckCollarBlue = синий ошейник - .desc = Ошейник синего цвета, с кольцом для поводка. Зачем он вам? -ent-ClothingNeckCollarPink = розовый ошейник - .desc = Ошейник розового цвета, с кольцом для поводка. Зачем он вам? -ent-ClothingNeckCollarGreen = зелёный ошейник - .desc = Ошейник зелёного цвета, с кольцом для поводка. Зачем он вам? \ No newline at end of file + .desc = Ошейник, с кольцом для поводка. Зачем он вам? \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Alerts/alerts.yml b/Resources/Prototypes/_Fish/Alerts/alerts.yml index 093c9de73b3..6c7b79e3cf0 100644 --- a/Resources/Prototypes/_Fish/Alerts/alerts.yml +++ b/Resources/Prototypes/_Fish/Alerts/alerts.yml @@ -2,7 +2,7 @@ id: Collared clickEvent: !type:RemoveCollarAlertEvent icons: - - sprite: /Textures/_Fish/Clothing/Neck/Misc/petcollar.rsi + - sprite: /Textures/_Fish/Clothing/Neck/Misc/collar.rsi state: icon name: alerts-collared-name description: alerts-collared-desc \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml index ddc2f10eec8..ff36f488b6a 100644 --- a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml +++ b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml @@ -4,11 +4,12 @@ id: ClothingNeckCollarBase name: grey collar description: A collar with a leash ring. Why do you need it? + categories: [ DoNotMap ] components: - type: Item size: Small sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#DCDCDC" + # color: "#DADADA" - type: Butcherable butcheringType: Knife spawned: @@ -24,95 +25,13 @@ clothingVisuals: collar: - state: equipped-NECK - color: "#DCDCDC" + # color: "#DADADA" - type: Sprite sprite: _Fish/Clothing/Neck/Misc/collar.rsi - scale: 0.75, 0.75 layers: - state: icon - color: "#DCDCDC" + # color: "#DADADA" - type: Collar - type: Tag tags: - - Recyclable - -# Colored -- type: entity - parent: ClothingNeckCollarBase - id: ClothingNeckCollarRed - name: red collar - components: - - type: Item - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#ff6b6b" - - type: Clothing - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - clothingVisuals: - collar: - - state: equipped-NECK - color: "#ff6b6b" - - type: Sprite - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - layers: - - state: icon - color: "#ff6b6b" - -- type: entity - parent: ClothingNeckCollarBase - id: ClothingNeckCollarBlue - name: blue collar - components: - - type: Item - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#6bcbff" - - type: Clothing - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - clothingVisuals: - collar: - - state: equipped-NECK - color: "#6bcbff" - - type: Sprite - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - layers: - - state: icon - color: "#6bcbff" - -- type: entity - parent: ClothingNeckCollarBase - id: ClothingNeckCollarPink - name: pink collar - components: - - type: Item - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#FF7AF0" - - type: Clothing - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - clothingVisuals: - collar: - - state: equipped-NECK - color: "#FF7AF0" - - type: Sprite - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - layers: - - state: icon - color: "#FF7AF0" - -- type: entity - parent: ClothingNeckCollarBase - id: ClothingNeckCollarGreen - name: green collar - components: - - type: Item - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - color: "#82ffa3" - - type: Clothing - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - clothingVisuals: - collar: - - state: equipped-NECK - color: "#82ffa3" - - type: Sprite - sprite: _Fish/Clothing/Neck/Misc/collar.rsi - layers: - - state: icon - color: "#82ffa3" \ No newline at end of file + - Recyclable \ No newline at end of file diff --git a/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml index 2bc8f2eb474..1865611cbbb 100644 --- a/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml +++ b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml @@ -4,13 +4,14 @@ id: LeashBase name: grey leash description: A leash roulette that attaches to the collar. + categories: [ DoNotMap ] components: - type: Sprite sprite: _Fish/Objects/Fun/leash.rsi - scale: 0.75, 0.75 + # scale: 0.75, 0.75 layers: - state: icon - color: "#DCDCDC" + # color: "#DCDCDC" - state: leash-icon - type: Item sprite: _Fish/Objects/Fun/leash.rsi @@ -18,93 +19,8 @@ inhandVisuals: left: - state: inhand-left - color: "#DCDCDC" + # color: "#DCDCDC" right: - state: inhand-right - color: "#DCDCDC" - - type: Leash - -# Colored -- type: entity - parent: LeashBase - id: LeashRed - name: red leash - components: - - type: Sprite - sprite: _Fish/Objects/Fun/leash.rsi - layers: - - state: icon - color: "#ff6b6b" - - state: leash-icon - - type: Item - sprite: _Fish/Objects/Fun/leash.rsi - inhandVisuals: - left: - - state: inhand-left - color: "#ff6b6b" - right: - - state: inhand-right - color: "#ff6b6b" - -- type: entity - parent: LeashBase - id: LeashBlue - name: blue leash - components: - - type: Sprite - sprite: _Fish/Objects/Fun/leash.rsi - layers: - - state: icon - color: "#6bcbff" - - state: leash-icon - - type: Item - sprite: _Fish/Objects/Fun/leash.rsi - inhandVisuals: - left: - - state: inhand-left - color: "#6bcbff" - right: - - state: inhand-right - color: "#6bcbff" - -- type: entity - parent: LeashBase - id: LeashPink - name: pink leash - components: - - type: Sprite - sprite: _Fish/Objects/Fun/leash.rsi - layers: - - state: icon - color: "#FF7AF0" - - state: leash-icon - - type: Item - sprite: _Fish/Objects/Fun/leash.rsi - inhandVisuals: - left: - - state: inhand-left - color: "#FF7AF0" - right: - - state: inhand-right - color: "#FF7AF0" - -- type: entity - parent: LeashBase - id: LeashGreen - name: green leash - components: - - type: Sprite - sprite: _Fish/Objects/Fun/leash.rsi - layers: - - state: icon - color: "#82ffa3" - - state: leash-icon - - type: Item - sprite: _Fish/Objects/Fun/leash.rsi - inhandVisuals: - left: - - state: inhand-left - color: "#82ffa3" - right: - - state: inhand-right - color: "#82ffa3" \ No newline at end of file + # color: "#DCDCDC" + - type: Leash \ No newline at end of file diff --git a/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png b/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png index 969979f5b511303c469c6a64c4b61b06e7fda321..2dbe242a02496f207451d7409360bfd8bb953f5e 100644 GIT binary patch delta 120 zcmeyxc${%USYeE(i(^Pd+}m4*Tn8LDSPty|Z(ntJ=>!qSA{F;EE7r(rS!RZY`dM2G z-6x%~JbyE4{WO)W*U#qj_HJ@9mtlYav!c@V@o!h2xfZ*>ZU0yK4^wSH;tjPjH9zJ5 T1eeQw193fF{an^LB{Ts5x>+zu delta 172 zcmX@k_=|BuSbe{zi(^Pd+}m@8T+I#w4i`@fx_ommn^$@BnD84>og7yd{SBExDq)HO z&JXtHEt_F4$iVPnTaZ`vyyclyGxxu}bKWxkR)0rFRQBxWJFiBq@3;STdefgZ{0tCK z|D-H#kMi?7L0%Jsx=Le{Z*P-0{&8{ie66Kh_Pt;Hde;rr@3wu9I}#=I{xX7e{ID;7 W!D~A8tJ9X>Ag-sYpUXO@geCxDy-q;@ diff --git a/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/icon.png b/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/icon.png index 34a6c2f7f48b2195abdf2964c5e9a569e3104ad8..39d1703b544547cefcb4a09fcf26e4a3369de59a 100644 GIT binary patch delta 845 zcmV-T1G4;(1(gSoReu92Nkl3{ZCnKZI1O+vt=~BePN{c&*g|f#i zYr7ywKrOUVQk;ljBO(YA1dB8lHo?+b@3`a4G-~#&1Ywy0(~VBRXvkzTtI5oNg{;IO z*+iQwHc$C|@BhyGfBf?SEn2kr*6&+ra@Iz5JFHa7U8-s48s7=^RR6jcXxLH zfQyR@0Km}D5c>N1d>tJfpRca2_B_@v)SbZ8)YLy?V`CpWJ3Dc6a|6>f|J>Qxne^&v zv9hxAhbW4l1_lOTSr$%DPfrdH4}bIOP%psT+}vL*%YVv|NCcXuAs&y{)HPytb(Mz0 zVVs?v!Ev0wlgZ>quZDOF06++l2_evR{oNl2fOtI4=(-L<2nZpvw~u-Oy1Kd`NfK05 z^|s!Os;cn&{SZab+XCtZU>F9nEJKncZ|luSk_1_n!7#N4Su=p^x?e5J!otD=qR}Yz z_M0UdjepYl`FU8D1pr^Yebjq|0I;>SMJFdG!Sg(_*(}=I+c7#iT3269I-RDrZ6go} zz%)%99UWnFbF=2z=Y3iO+qO>(!}t&ig)lNQfrOokpGAJet9wM(yWH8sal&HDPfJw86p_xAQe5Ck}m0{~!I_GK+Q zj`OO1e}4~M*KvM+{%w1E`-iKzVSw-K?d?&Y&xc?z2tg3g-`@{PpWi3LFu11u-E)_c1sjFZ&aS)e)bS?gff{PBx z$JyDL0Jyrk(py|#U+WT+$z-d&zZ{ary(=-LZ@$s>;PgMw|D4M5f zN)$!#4PzY{-6Bb=}5K%~=I^UAGZM5owy{w;{bK1}GxU zb^WKv5LGAvMxzmq;}oS|#c>==(^Rz54Fo}uzjVJz7={Evpv&a|0KV^I7)Jh_{uRS8 zgzx*h4Ek4KS(Z3GJ>}@=h>wpC=JPoK7Z(>ldLKN`6Mq0FCnxOe>@b~9d3t){_Vza4 z@2|2#L;e{7f^P(o#&Z2)vCv1>V_BBCzrPnrl888tMG?>Ql)4IiCXQo~B#H1mPZU2t zN#%)(j{>0E?Uqh)aB!g1RsHAZXMoD9my`sF3<2apioCzSD|M^YQfgMlz*=)Q1(4Xb zonPDj{y#pf2}Hs0_4PH^hQr}TkBv>8Vwg>QWm;tGL5XeK8`lhn!>vNr)YM2m0pUM3 UwAAEPdH?_b07*qoM6N<$f?9MPa{vGU From 9ebb9f0f4b94a56de46256f5be409d99429e1bcc Mon Sep 17 00:00:00 2001 From: dng-cher Date: Fri, 21 Aug 2026 08:23:39 +0500 Subject: [PATCH 07/10] fx-6 --- .../VendingMachines/petomat_catalog.yml | 12 ++--------- .../Machines/petmate.rsi/deny-unshaded.png | Bin 436 -> 435 bytes .../Structures/Machines/petmate.rsi/meta.json | 20 +++++++++--------- .../Machines/petmate.rsi/normal-unshaded.png | Bin 959 -> 928 bytes 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml b/Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml index faa3a15a3cc..3c1d67d3bcb 100644 --- a/Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml +++ b/Resources/Prototypes/_Fish/Catalog/VendingMachines/petomat_catalog.yml @@ -2,14 +2,6 @@ id: PetMateVendInventory startingInventory: # Collars - ClothingNeckCollarBase: 2 - ClothingNeckCollarRed: 2 - ClothingNeckCollarBlue: 2 - ClothingNeckCollarPink: 2 - ClothingNeckCollarGreen: 2 + ClothingNeckCollarBase: 3 # Leashs - LeashBase: 2 - LeashRed: 2 - LeashBlue: 2 - LeashPink: 2 - LeashGreen: 2 \ No newline at end of file + LeashBase: 3 \ No newline at end of file diff --git a/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/deny-unshaded.png b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/deny-unshaded.png index fa93aa5f110b8e32a4fc642f17e24d7384b5276e..b53226b4708d67d0f8330974017a74484bf9cde9 100644 GIT binary patch delta 358 zcmV-s0h#`^1G58=R)0uIL_t(&L+#W-YQjJi2H@Y;N~qEVA_>x!ULyP6qo?aVy3Zk0 zx~O76OiEfpYO$NiFwP+4C7B5A``|4SC;swh9Dp-NjFe`%oKxMcVAa(Zya_+0L+ zg>vqkWw*$u6<;9QSO0_8I0LH8vt8H_tpNSx?cfw95xY*!?ez>ummB=N9gInr8-1x2 zHu7s1kfeJ!$r0J0FvdxaNYcIjNT;ry&lqEjG4|*D0*IH|7@)w1kpKVy07*qoM6N<$ Ef+#bzEC2ui delta 359 zcmV-t0hs=?1GEE>R)0xJL_t(&L+#W-YJ)%&2H@W|4bp^GFwsTHI(v`Md-Qa@M~~n> zi!4fLpoUOeY!RDcH{&pQ67Z>GpnV^_g=WkjekTJ6A%qY@2=OmD^u{T#_mAa`JHgl8 z75xa;FW>mcKM_7XH2yv=Tlp9C#wmg4BM(NnE#2DW!3crp(|<>LWh=jN0RZ5IGhF(U z+IZoNF15l&KC1vs$B4@fFdggB*F!LJYts_1fUH2|ED-NjKvp2$tq?g2AS>ufJ5(mJ zLXW;~iC2JXJDM*Mmm5U$W#>1*Vq{mlPydFNcm)`(n>(S)8(>jcRkq%J9rAevkk-w& zF$m+_Im+&k&tNM+wQv3h&v6D+nMb>@C0+saLG&%pLR$NY`8ZJipZ@U2o}9 zD{SO9E+9$waFYYxoxC<~azK*q=_9>5cfJrp2qDCu^9y{L+8EAaJjDP2002ovPDHLk FV1igHruYB= diff --git a/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json index 91417a0d624..8130a68fbf4 100644 --- a/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json +++ b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/meta.json @@ -20,14 +20,14 @@ "name": "normal-unshaded", "delays": [ [ - 0.5, - 0.2, - 1, - 0.2, - 1, - 0.2, - 1, - 0.2 + 0.7, + 0.5, + 1.2, + 0.5, + 1.2, + 0.5, + 1.2, + 0.5 ] ] }, @@ -35,8 +35,8 @@ "name": "deny-unshaded", "delays": [ [ - 0.7, - 0.7 + 0.8, + 0.8 ] ] } diff --git a/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/normal-unshaded.png b/Resources/Textures/_Fish/Structures/Machines/petmate.rsi/normal-unshaded.png index 33b3a7c55d859419b6206fa32ab6bda95f245415..908ea64f3c171b88652bea15e29bdb11ed1e031c 100644 GIT binary patch delta 855 zcmV-d1E~DJ2cQR#R(~}~L_t(|UhUn{N*h5G2H<1YEzRg-bglpr}X&J z@RZVQF{h+|nXlPmuAXo1dA68)=gS6QPrZU2ATirKP%@bkWv76;F(s4f|JMpi`Ef)P zM_&1|0oc1<`+o$a`@$(GyjD;+ap^u^KKZf%*t=f&1eo1NadgNvuQjl|WHKd+qr>0n zjbsC`cfIxr$g<2%McDo3xt`A_$OiBZ`uQe5 z-}#>Xd=Ycnv&<^)Up4^tTY&vusKUzUe^$ZX0rHdYnJ*eZhdN>lvQ_N z_s~Y>4Q+}e`^v_U4Ipa=n;n?<^_5Nl-~IW{SA0Gp1la(x-DcXs`4cNzCxHLI0qiuW zJy4$zfNTJK*Yl@Slukfl*IQlvrjiXH+j-_UkShA|A_4mObrTJsjs5((IS-)jetg~e h00000007Vu{{ShkZ*#Z(dSn0q002ovPDHLkV1kg~xt#z2 delta 886 zcmV-+1Bv{g2fqi9R)17UL_t(|UhUnnZqrZ{2H+Dgl}SU1nkE@g=Zuy72#k#EJRLhD zBaaZNV}=f>q7sf;LMdPsBQxOZ9A7umJ+<#~?f##1McTUkvCh3!k$zJm0000000000 zH3Rz{yL^59K065a>DQ}g_J?TrTCWJ`+ps~%*GQ+W&yp-!pcf! z0nNq}`%@a#*L>#z5D|^%NAz^EUs>b%k$tHbeasgPfJS?}mxerRnVlD_jr=>;J;?@e zbGv(KxVfd}dPULTxLC{eN$Oinl9o9vB=iA&OA)ybC)z~1%BC&27J3WIH~d98uvMbiaQ z7;OJe_aqyDz3a74K%QlGB0mW#c4gPdmkq$)^~xuJ=S9=ScK0dgSF&s5iw3YcyP&_H zzg5=e?83g(i$3N%4c_k4YrbHgW&50o*Zt?S8)LQ&oi62f7t-oPXYG#LKRj%e_I9n4p5x@n)#vu_~^$=Je2&Ct3x`u zI&380c>rzq;~#rI000000000000000004B4bAO-RS3llW+xfmYw<3J?<6X6#?~C&Q z$WuRFg6H$+6twbv5DlQzM6;KMJd5`;i=Jvn$p*kWk-Q;XryD6^_c2*dwX0+UVE=Ev zX(_Ajz|ppi%p2Mi2KJSmAsay64mLZm=<6$;0KWT+ov-+Of)BC*9{RG^jnF;DKxaeAn}*Q Date: Fri, 21 Aug 2026 21:51:55 +0500 Subject: [PATCH 08/10] feachi-2 --- .../_Fish/Leash/Systems/LeashSystem.cs | 64 ++++++++++-------- .../_Fish/Leash/Components/CollarComponent.cs | 3 + .../_fish/entities/fun/alerts/leash.ftl | 1 + .../_Fish/Entities/Clothing/Neck/collars.yml | 2 + .../Neck/Misc/collar.rsi/equipped-NECK.png | Bin 199 -> 256 bytes .../Clothing/Neck/Misc/collar.rsi/icon.png | Bin 917 -> 928 bytes 6 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs index ff4e3794ba3..bc21ab85e3c 100644 --- a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs +++ b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs @@ -50,6 +50,7 @@ public sealed partial class LeashSystem : EntitySystem [Dependency] private SharedPopupSystem _popup = default!; [Dependency] private SharedTransformSystem _transform = default!; [Dependency] private IGameTiming _timing = default!; + [Dependency] private IRobustRandom _random = default!; private readonly HashSet _allowedCollarUnequips = new(); @@ -394,46 +395,55 @@ private void OnRemoveCollarAlert(EntityUid uid, CollarWearerComponent component, args.Handled = true; } - private void OnRemoveCollarDoAfter(EntityUid uid, CollarWearerComponent component, RemoveCollarDoAfterEvent args) +private void OnRemoveCollarDoAfter(EntityUid uid, CollarWearerComponent component, RemoveCollarDoAfterEvent args) +{ + if (args.Handled) + return; + + args.Handled = true; + + if (component.Collar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer != uid) { - if (args.Handled) - return; + return; + } - args.Handled = true; + if (args.Cancelled) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); + return; + } - if (component.Collar is not { Valid: true } collarUid || - !TryComp(collarUid, out var collar) || - collar.Wearer != uid) + if (collar.RemoveSuccessChance < 1f) + { + if (_random.NextFloat() > collar.RemoveSuccessChance) { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail-chance"), uid, uid, PopupType.SmallCaution); return; } + } - if (args.Cancelled) + EntityUid? removedItem; + _allowedCollarUnequips.Add(collarUid); + try + { + if (!_inventory.TryUnequip(uid, uid, "neck", out removedItem, checkDoafter: false)) { _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); return; } + } + finally + { + _allowedCollarUnequips.Remove(collarUid); + } - EntityUid? removedItem; - _allowedCollarUnequips.Add(collarUid); - try - { - if (!_inventory.TryUnequip(uid, uid, "neck", out removedItem, checkDoafter: false)) - { - _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); - return; - } - } - finally - { - _allowedCollarUnequips.Remove(collarUid); - } - - if (removedItem != null) - _hands.PickupOrDrop(uid, removedItem.Value); + if (removedItem != null) + _hands.PickupOrDrop(uid, removedItem.Value); - _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), uid, uid); - } + _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), uid, uid); +} private void HandleLeashReleased(EntityUid uid, LeashComponent component, EntityUid user) { diff --git a/Content.Shared/_Fish/Leash/Components/CollarComponent.cs b/Content.Shared/_Fish/Leash/Components/CollarComponent.cs index 178452cd28e..1b9e1320cee 100644 --- a/Content.Shared/_Fish/Leash/Components/CollarComponent.cs +++ b/Content.Shared/_Fish/Leash/Components/CollarComponent.cs @@ -11,6 +11,9 @@ public sealed partial class CollarComponent : Component [DataField] public ProtoId Alert = "Collared"; + + [DataField] + public float RemoveSuccessChance = 1f; public EntityUid? Wearer; public EntityUid? AttachedLeash; diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl index c864d049ce2..4076864593c 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl @@ -7,6 +7,7 @@ leash-detached-target = Поводок отстегнули от вашего о leash-choking = Ошейник впивается в шею и душит вас! leash-remove-collar-start = Вы начинаете возиться с застёжкой ошейника. leash-remove-collar-fail = Вам не удаётся снять ошейник. +leash-remove-collar-fail-chance = Попытка снять ошейник не увенчалась успехом. leash-remove-collar-success = Вам удаётся снять ошейник. leash-remove-collar-blocked = Сначала нужно расстегнуть ошейник. leash-cannot-pick-up = Поводок натянулся. diff --git a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml index ff36f488b6a..ef1684f9529 100644 --- a/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml +++ b/Resources/Prototypes/_Fish/Entities/Clothing/Neck/collars.yml @@ -32,6 +32,8 @@ - state: icon # color: "#DADADA" - type: Collar + breakoutTime: 4 + removeSuccessChance: 0.7 - type: Tag tags: - Recyclable \ No newline at end of file diff --git a/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png b/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/equipped-NECK.png index 2dbe242a02496f207451d7409360bfd8bb953f5e..6d2031f6324fa3b05e527c62806d6e1243a9cf21 100644 GIT binary patch delta 178 zcmX@k*uXR)tbVGei(^Pd+}oRqeMbyryz{-0g9J;-bQ)^Br)fke=$g8%2=9}Rw9`R!WNk|{}%(;mt& zK){cH-~0IgKfa&(W!c01b?0`T|15La#5G&@TB+Jwf75S!*gzbTmKiN b=RkQCBj-AE*`pjn4?%oSS3j3^P6c3l delta 120 zcmZo*I?gyDtT4vY#WAEJ?(Ho@t^*DnEC+V~x39Xqbb^Ruk&1ho6>DU*EHgtx{j9Bp z?vu_~p1&EjewxbG>u2+MdpEh5%P>HISyAcw__r(1T#Mb`w*RaAhpDz8@rGKNnxFE2 Sg3IN;fw-QoelF{r5}E*kpfAt> diff --git a/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/icon.png b/Resources/Textures/_Fish/Clothing/Neck/Misc/collar.rsi/icon.png index 39d1703b544547cefcb4a09fcf26e4a3369de59a..40726e46086d5e17cd4d145775fbf059537a3c44 100644 GIT binary patch delta 855 zcmV-d1E~C!2cQR#R(~}~L_t(oN9~loFSC0X$3J&pq@{NeE!8FliAq??^Xv5^h(u$BYH$@^%tFO*|-)9g)wU4qq!||DZxn91% zZ?0Uq;D;Z6_`?XLfofP@URGb;b8~aw(s-e&W{=0C+uPgwcYkzrRBdZ(!{_s%)9C=% z+1bJ2a1f8jiN#_lio({`R#m!GYakE^s83H%bar+UkH_(NJoNPRpx5hJUtec%a1dFR zktB)7$48>kD0;n~wY9ZR^|hseQUZa1DvBa@yPZOzKsK8tm&(qe0=KFlF1||Cno@OcX!j=+)Qn4?ZVmFSw#~SN96PQ)c*c{>gwvazP=`%PP4MI zA^=QHO}#GR#>U16jhUI5*QsSP84eE*+1}p%P)8*LV}D~~s#dGT?RFE3#R!E$W$Qj; zc6L^EI-MLH9U;qd+2z!f1^_e~4H}IGNs_+(IY1~B5+q4_)hkEYXhTB-Hk*xTG+LVa zGSO%hv)PO&ilr&2WIzxEyk0Lho2@kUWo$MZUauEHC_l)u1{6ghlgThKF@fLje_gn* z;`jU2@qh7gGMP+i>Xq`gfTg7+b$ECfgTcV*X2 z?(Qy&i;HD@pYLfIa=9FdM1uDAc6xhzNu^RmA`vwfi!m@T0D!?@03e^w18{I~fYa&3 z;c#F!o4L8UL6Rg2g~H$O#NSZy5bf{p|LW-ISbwlsEY#H0V6|GQudgSc&ttdSX=-Ys zv9S?R6fqbKD2hU{Sfs0~3sDrgzrW}F{G4z&{7*0#{Hs*|g|cIL%l!PjIy5wd)oR6L zGQFN$tyYUx`|Mwq<#+LihX*7{A{-9qgTbKjonQ%{8hBxIb5qsnbT}LiOePbpt*xl) z^H6>Yg22Va1<7QRbUID3SY&c?vMTMWir%uix~d9-@Zqq?vW%*#OixdLS@Vzb9k-wL heEV+v@WcO;{{U}mJTmFi^11*3002ovPDHLkV1l2)tYQEF delta 844 zcmV-S1GD^~2bBkqR(~oHC!O~jqxZ}(;YWA!IVVMEbjZVO5$Ye6B$;^L+ti&PN zM4Kx%Px*cC|IYh={PO`VTD17tVZ4P}OiWDp6h*1b&dz?}d4K+wrKKhKd_Dj`KA*?_ z{{D;Bb=`lH$>gt9$A3su0b;QjEtks}92|ro2nYlM7#8F?kw~zorw5v*K~+@{LQpIg;kqsi!vN3oux%T6cXt4Q zi;D{Yz|hbT`uh5O9UUE?udc54Jk~GNoxs%8)IVcmV;?#@J8^S!1JgAB+}YWg^y+G{ zva<4rD2ks31_oeR7EVu3PYw?cfAi{4FTmW~++Qrq%72ka1e&HH9*@`5HDYyjm4?G% zoSmJ)ah$)C$>c|`hIk7AKnRfuA<%XG-5&>lcs$PNx(-4J2qCh!k9q;Ry1F1q5>!?7 zw%&}Ys_^^$5Jl130_p`|7zVN|Ly{zK>&-}#1X-5BFtrC+Gl1*5UoFeR!omWg(J1xy znCCnqPt^E|TIEZW=KF*-V0S6@v!ou;;JBM=C{ zG)){G9bt2Gv*y|7eOd$CwoeSh_z(((FfuZNLZN_kI!!fA`+a6+#w!N^92^`V91iaW zgF*QHe%#*PLf3UXK0cm!wN0_Hv2m#=3e99P)PJ%pn#<*ADwU#!VbD^kM88uim8fAD zG?hxxTrNi~%c7Y~h8`au)3vp=ORsM=HOErT`ue&(K0eO(_Vz*$1UQZZ0AN}6Wi307 z^QwM-e-B;PaejXOZF_tBhpV_@fbZ<>?NOi4hhQ)WK@ia2-w#Tk-zUQ`xW2wdKA(qa znnieedcxx3VpHBtiK^M%-K7k}fRNWekmER@lwx^#`OTjHEAL6$=*+wKMvE3dC;tHm WH&2`}E%e3!0000 Date: Fri, 21 Aug 2026 22:01:58 +0500 Subject: [PATCH 09/10] fux-8 --- Content.Server/_Fish/Leash/Systems/LeashSystem.cs | 1 + MSBuild/Content.props | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs index bc21ab85e3c..af47e5782a1 100644 --- a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs +++ b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs @@ -31,6 +31,7 @@ using Robust.Shared.Physics; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Robust.Shared.Random; namespace Content.Server._Fish.Leash.Systems; diff --git a/MSBuild/Content.props b/MSBuild/Content.props index de44d3ccc65..9d9d4bb715b 100644 --- a/MSBuild/Content.props +++ b/MSBuild/Content.props @@ -12,6 +12,6 @@ true - CS0618,CS0672,CS0612,CS1062,CS1064,NU1903 + CS0618,CS0672,CS0612,CS1062,CS1064,NU1903,NU1900 From ab538f5af963520a04f167a7eb74b1f250b92220 Mon Sep 17 00:00:00 2001 From: dng-cher Date: Sat, 22 Aug 2026 11:51:47 +0500 Subject: [PATCH 10/10] fix-featc-9 --- .../_Fish/Leash/Components/LeashComponent.cs | 6 +- .../_Fish/Leash/Systems/LeashSystem.cs | 141 ++++++++++++------ .../_fish/entities/fun/alerts/leash.ftl | 3 +- .../_Fish/Entities/Objects/Fun/leash.yml | 4 +- 4 files changed, 107 insertions(+), 47 deletions(-) diff --git a/Content.Server/_Fish/Leash/Components/LeashComponent.cs b/Content.Server/_Fish/Leash/Components/LeashComponent.cs index 392e3be33b2..1101d099f46 100644 --- a/Content.Server/_Fish/Leash/Components/LeashComponent.cs +++ b/Content.Server/_Fish/Leash/Components/LeashComponent.cs @@ -10,7 +10,7 @@ public sealed partial class LeashComponent : Component { DamageDict = { - ["Asphyxiation"] = 1, + ["Asphyxiation"] = 0.75, }, }; @@ -32,10 +32,10 @@ public sealed partial class LeashComponent : Component [DataField] public float PullForce = 32f; - [DataField] + [DataField("modes")] public List Modes = new() { 1f, 2.5f, 4f, 5.5f, 7f }; - [DataField] + [DataField("currentModeIndex")] public int CurrentModeIndex = 2; public EntityUid? AttachedCollar; diff --git a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs index af47e5782a1..f3ff605fe10 100644 --- a/Content.Server/_Fish/Leash/Systems/LeashSystem.cs +++ b/Content.Server/_Fish/Leash/Systems/LeashSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Disposal.Unit; using Content.Shared.Disposal.Unit.Events; using Content.Shared.DoAfter; +using Content.Shared.Examine; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; @@ -51,7 +52,7 @@ public sealed partial class LeashSystem : EntitySystem [Dependency] private SharedPopupSystem _popup = default!; [Dependency] private SharedTransformSystem _transform = default!; [Dependency] private IGameTiming _timing = default!; - [Dependency] private IRobustRandom _random = default!; + [Dependency] private IRobustRandom _random = default!; private readonly HashSet _allowedCollarUnequips = new(); @@ -71,6 +72,9 @@ public override void Initialize() SubscribeLocalEvent(OnLeashDropped); SubscribeLocalEvent(OnLeashInsertedIntoContainer); SubscribeLocalEvent(OnLeashTerminating); + SubscribeLocalEvent(OnLeashUseInHand); + SubscribeLocalEvent(OnLeashExamined); + SubscribeLocalEvent>(OnLeashGetVerbs); SubscribeLocalEvent(OnWearerMoveInput); SubscribeLocalEvent(OnCollarWearerPickupAttempt); @@ -95,6 +99,55 @@ public override void Update(float frameTime) } } + private void OnLeashUseInHand(EntityUid uid, LeashComponent component, UseInHandEvent args) + { + if (args.Handled) + return; + + if (component.Modes.Count == 0) + return; + + args.Handled = true; + + component.CurrentModeIndex = (component.CurrentModeIndex + 1) % component.Modes.Count; + var newDistance = component.Modes[component.CurrentModeIndex]; + component.MaximumDistance = newDistance; + + _popup.PopupEntity(Loc.GetString("leash-mode-changed", ("length", newDistance)), args.User, args.User); + } + + private void OnLeashGetVerbs(EntityUid uid, LeashComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + if (component.Modes.Count < 2) + return; + + var verb = new AlternativeVerb + { + Act = () => + { + component.CurrentModeIndex = (component.CurrentModeIndex + 1) % component.Modes.Count; + var newDistance = component.Modes[component.CurrentModeIndex]; + component.MaximumDistance = newDistance; + _popup.PopupEntity(Loc.GetString("leash-mode-changed", ("length", newDistance)), args.User, args.User); + }, + Text = Loc.GetString("leash-switch-mode-verb"), + Priority = 1, + }; + args.Verbs.Add(verb); + } + + private void OnLeashExamined(EntityUid uid, LeashComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + var currentLength = component.MaximumDistance; + args.PushMarkup(Loc.GetString("leash-examine-length", ("length", currentLength))); + } + private void OnCollarEquipped(EntityUid uid, CollarComponent component, GotEquippedEvent args) { if (args.Slot != "neck") @@ -218,12 +271,8 @@ private void OnRemoveCollarDoAfterVerb(RemoveCollarDoAfterEvent args) if (!_hands.TryPickupAnyHand(user, removedItem.Value)) { Transform(removedItem.Value).AttachToGridOrMap(); - _popup.PopupEntity(Loc.GetString("leash-remove-collar-dropped"), user, user); - } - else - { - _popup.PopupEntity(Loc.GetString("leash-remove-collar-transferred"), user, user); } + _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), user, user); } } @@ -251,6 +300,13 @@ private void OnLeashAfterInteract(EntityUid uid, LeashComponent component, After return; } + if (collar.Wearer == args.User) + { + _popup.PopupEntity(Loc.GetString("leash-cannot-attach-to-self"), args.User, args.User, PopupType.SmallCaution); + args.Handled = true; + return; + } + AttachLeash(uid, component, collarUid, collar, args.User); args.Handled = true; } @@ -396,55 +452,55 @@ private void OnRemoveCollarAlert(EntityUid uid, CollarWearerComponent component, args.Handled = true; } -private void OnRemoveCollarDoAfter(EntityUid uid, CollarWearerComponent component, RemoveCollarDoAfterEvent args) -{ - if (args.Handled) - return; - - args.Handled = true; - - if (component.Collar is not { Valid: true } collarUid || - !TryComp(collarUid, out var collar) || - collar.Wearer != uid) + private void OnRemoveCollarDoAfter(EntityUid uid, CollarWearerComponent component, RemoveCollarDoAfterEvent args) { - return; - } + if (args.Handled) + return; - if (args.Cancelled) - { - _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); - return; - } + args.Handled = true; - if (collar.RemoveSuccessChance < 1f) - { - if (_random.NextFloat() > collar.RemoveSuccessChance) + if (component.Collar is not { Valid: true } collarUid || + !TryComp(collarUid, out var collar) || + collar.Wearer != uid) { - _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail-chance"), uid, uid, PopupType.SmallCaution); return; } - } - EntityUid? removedItem; - _allowedCollarUnequips.Add(collarUid); - try - { - if (!_inventory.TryUnequip(uid, uid, "neck", out removedItem, checkDoafter: false)) + if (args.Cancelled) { _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); return; } - } - finally - { - _allowedCollarUnequips.Remove(collarUid); - } - if (removedItem != null) - _hands.PickupOrDrop(uid, removedItem.Value); + if (collar.RemoveSuccessChance < 1f) + { + if (_random.NextFloat() > collar.RemoveSuccessChance) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail-chance"), uid, uid, PopupType.SmallCaution); + return; + } + } + + EntityUid? removedItem; + _allowedCollarUnequips.Add(collarUid); + try + { + if (!_inventory.TryUnequip(uid, uid, "neck", out removedItem, checkDoafter: false)) + { + _popup.PopupEntity(Loc.GetString("leash-remove-collar-fail"), uid, uid, PopupType.SmallCaution); + return; + } + } + finally + { + _allowedCollarUnequips.Remove(collarUid); + } - _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), uid, uid); -} + if (removedItem != null) + _hands.PickupOrDrop(uid, removedItem.Value); + + _popup.PopupEntity(Loc.GetString("leash-remove-collar-success"), uid, uid); + } private void HandleLeashReleased(EntityUid uid, LeashComponent component, EntityUid user) { @@ -453,6 +509,7 @@ private void HandleLeashReleased(EntityUid uid, LeashComponent component, Entity if (component.AttachedCollar != null) DetachLeash(uid, component); + StopLeashPull(uid, component); RemoveHolderLeash(uid, component.Holder); component.Holder = null; diff --git a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl index 4076864593c..28ecb00619e 100644 --- a/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_fish/entities/fun/alerts/leash.ftl @@ -12,4 +12,5 @@ leash-remove-collar-success = Вам удаётся снять ошейник. leash-remove-collar-blocked = Сначала нужно расстегнуть ошейник. leash-cannot-pick-up = Поводок натянулся. leash-cannot-carry = Поводок натянулся. -leash-mode-changed = Длина поводка установлена на { $length } метр. +leash-mode-changed = Длина поводка установлена на { $length } м. +leash-cannot-attach-to-self = Вы не можете пристегнуть поводок к собственному ошейнику. diff --git a/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml index 1865611cbbb..ece00668717 100644 --- a/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml +++ b/Resources/Prototypes/_Fish/Entities/Objects/Fun/leash.yml @@ -23,4 +23,6 @@ right: - state: inhand-right # color: "#DCDCDC" - - type: Leash \ No newline at end of file + - type: Leash + modes: [1, 2.5, 4, 5.5, 7] + currentModeIndex: 2 \ No newline at end of file