Skip to content

Commit

Permalink
Felinid Soft Thieving + Trait (#500)
Browse files Browse the repository at this point in the history
<!--
This is a semi-strict format, you can add/remove sections as needed but
the order/format should be kept the same
Remove these comments before submitting
-->

# Description

<!--
Explain this PR in as much detail as applicable

Some example prompts to consider:
How might this affect the game? The codebase?
What might be some alternatives to this?
How/Who does this benefit/hurt [the game/codebase]?
-->

Reworks felinid thieving to no longer act like passive thieving gloves
which grant invisible stripping (hereon referred to as hard stealing),
in favour of soft thievery (hereon referred to as soft stealing).

Soft thievery comprises of the following:
- A smaller popup, with the thief anonymised.
- A visible doafter bar
- A 33% faster strip speed, that stacks with Thieving gloves
- An additional ability to identify hidden items to better plan your
course of action

You no longer need to completely avoid felinids to maintain your
precious items as long as you pay attention. For a felinid to utilise
their thieving passive, they are encouraged to exploit any distractions
to make moves on a target. If there is none, create one through
conversation or other forms of player interaction. If you are suspected,
persuade your victim that the thief is in fact, the other person.

A faster strip speed makes thief bonuses diegetic to other players, and
also improves the value proposition of thieving gloves on someone who
already has thieving bonuses.

Any other race can also gain soft thievery via a moderate costing trait.
Non-felinid thieves are encouraged to exploit any felinids as a
scapegoat.

---

# TODO

<!--
A list of everything you have to do before this PR is "complete"
You probably won't have to complete everything before merging but it's
good to leave future references
-->
Code
- [X] IgnoreStripHidden - allows thieves to look into peoples pockets
- [X] StripTimeMultiplier - stripping at a multiplicative rate helps
strip bags/belts which creates trait value
- [X] Stealthy > Stealth - rather than a bool, distinguishes stealth
levels as an enum

Balance
- [X] Soft thieves can identify items in pockets, which creates player
agency
- [X] Soft thieves steal 33% faster, which stacks with thieving gloves
- [X] Victims to soft stealing get a smaller popup, useful if they're
preoccupied
- [X] Soft thievery is a trait, which Felinids get for free
- [X] Felinids no longer hard steal items

Media
- [x] Attach media
---

<!--
This is default collapsed, readers click to expand it and see all your
media
The PR media section can get very large at times, so this is a good way
to keep it clean
The title is written using HTML tags
The title must be within the <summary> tags or you won't see it
-->

<details><summary><h1>Media</h1></summary>
<p>


![image](https://github.com/Simple-Station/Einstein-Engines/assets/69510347/c0c6e81f-21e2-48c5-a535-777c1f683ef7)
![video](https://www.youtube.com/embed/elDfOgAPmIs?si=yV5JjOaSYvurGZer)
</p>
</details>

---

# Changelog

<!--
You can add an author after the `:cl:` to change the name that appears
in the changelog (ex: `:cl: Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

:cl:
- add: Added the Thievery trait, which provides various soft stripping
bonuses
- tweak: Felinids no longer have passive thieving gloves, they instead
get the Thievery trait by default

---------

Signed-off-by: WarMechanic <69510347+WarMechanic@users.noreply.github.com>
Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com>
  • Loading branch information
WarMechanic and DEATHB4DEFEAT authored Jul 5, 2024
1 parent 692ceff commit e06045c
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 45 deletions.
5 changes: 4 additions & 1 deletion Content.Client/Inventory/StrippableBoundUserInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.Player;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
Expand All @@ -31,6 +32,7 @@ namespace Content.Client.Inventory
public sealed class StrippableBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly ExamineSystem _examine;
private readonly InventorySystem _inv;
private readonly SharedCuffableSystem _cuffable;
Expand Down Expand Up @@ -198,7 +200,8 @@ private void AddInventoryButton(EntityUid invUid, string slotId, InventoryCompon
var entity = container.ContainedEntity;

// If this is a full pocket, obscure the real entity
if (entity != null && slotDef.StripHidden)
if (entity != null && slotDef.StripHidden
&& !(EntMan.TryGetComponent<ThievingComponent>(_playerManager.LocalEntity, out var thiefcomponent) && thiefcomponent.IgnoreStripHidden))
entity = _virtualHiddenEntity;

var button = new SlotButton(new SlotData(slotDef, container));
Expand Down
51 changes: 30 additions & 21 deletions Content.Server/Strip/StrippableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public sealed class StrippableSystem : SharedStrippableSystem
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;

[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly ThievingSystem _thieving = default!;

// TODO: ECS popups. Not all of these have ECS equivalents yet.

Expand Down Expand Up @@ -251,15 +252,17 @@ private void StartStripInsertInventory(

var (time, stealth) = GetStripTimeModifiers(user, target, slotDef.StripTime);

if (!stealth)
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert", ("user", Identity.Entity(user, EntityManager)), ("item", user.Comp.ActiveHandEntity!.Value)), target, target, PopupType.Large);
bool hidden = stealth == ThievingStealth.Hidden;

var prefix = stealth ? "stealthily " : "";
if (!hidden)
StripPopup("strippable-component-alert-owner-insert", stealth, target, user: Identity.Entity(user, EntityManager), item: user.Comp.ActiveHandEntity!.Value);

var prefix = hidden ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot");

var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(true, true, slot), user, target, held)
{
Hidden = stealth,
Hidden = hidden,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
Expand Down Expand Up @@ -340,20 +343,22 @@ private void StartStripRemoveInventory(

var (time, stealth) = GetStripTimeModifiers(user, target, slotDef.StripTime);

if (!stealth)
bool hidden = stealth == ThievingStealth.Hidden;

if (!hidden)
{
if (slotDef.StripHidden)
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), target, target, PopupType.Large);
StripPopup("strippable-component-alert-owner-hidden", stealth, target, slot: slot);
else
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", item)), target, target, PopupType.Large);
StripPopup("strippable-component-alert-owner", stealth, target, user: Identity.Entity(user, EntityManager), item: item);
}

var prefix = stealth ? "stealthily " : "";
var prefix = hidden ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}strip the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s {slot} slot");

var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(false, true, slot), user, target, item)
{
Hidden = stealth,
Hidden = hidden,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
Expand All @@ -374,7 +379,7 @@ private void StripRemoveInventory(
EntityUid target,
EntityUid item,
string slot,
bool stealth)
bool hidden)
{
if (!CanStripRemoveInventory(user, target, item, slot))
return;
Expand All @@ -384,7 +389,7 @@ private void StripRemoveInventory(

RaiseLocalEvent(item, new DroppedEvent(user), true); // Gas tank internals etc.

_handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: stealth);
_handsSystem.PickupOrDrop(user, item, animateUser: hidden, animate: hidden);
_adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s {slot} slot");
}

Expand Down Expand Up @@ -446,12 +451,14 @@ private void StartStripInsertHand(

var (time, stealth) = GetStripTimeModifiers(user, target, targetStrippable.HandStripDelay);

var prefix = stealth ? "stealthily " : "";
bool hidden = stealth == ThievingStealth.Hidden;

var prefix = hidden ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");

var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(true, false, handName), user, target, held)
{
Hidden = stealth,
Hidden = hidden,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
Expand All @@ -471,7 +478,7 @@ private void StripInsertHand(
Entity<HandsComponent?> target,
EntityUid held,
string handName,
bool stealth)
bool hidden)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp))
Expand All @@ -481,7 +488,7 @@ private void StripInsertHand(
return;

_handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: user.Comp);
_handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: stealth, animate: stealth, handsComp: target.Comp);
_handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: hidden, animate: hidden, handsComp: target.Comp);
_adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");

// Hand update will trigger strippable update.
Expand Down Expand Up @@ -543,15 +550,17 @@ private void StartStripRemoveHand(

var (time, stealth) = GetStripTimeModifiers(user, target, targetStrippable.HandStripDelay);

if (!stealth)
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner", ("user", Identity.Entity(user, EntityManager)), ("item", item)), target, target);
bool hidden = stealth == ThievingStealth.Hidden;

if (!hidden)
StripPopup("strippable-component-alert-owner", stealth, target, user: Identity.Entity(user, EntityManager), item: item);

var prefix = stealth ? "stealthily " : "";
var prefix = hidden ? "stealthily " : "";
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}strip the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");

var doAfterArgs = new DoAfterArgs(EntityManager, user, time, new StrippableDoAfterEvent(false, false, handName), user, target, item)
{
Hidden = stealth,
Hidden = hidden,
AttemptFrequency = AttemptFrequency.EveryTick,
BreakOnDamage = true,
BreakOnTargetMove = true,
Expand All @@ -572,7 +581,7 @@ private void StripRemoveHand(
Entity<HandsComponent?> target,
EntityUid item,
string handName,
bool stealth)
bool hidden)
{
if (!Resolve(user, ref user.Comp) ||
!Resolve(target, ref target.Comp))
Expand All @@ -582,7 +591,7 @@ private void StripRemoveHand(
return;

_handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: target.Comp);
_handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: stealth, handsComp: user.Comp);
_handsSystem.PickupOrDrop(user, item, animateUser: hidden, animate: hidden, handsComp: user.Comp);
_adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");

// Hand update will trigger strippable update.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedStrippableSystem _strippable = default!;
[Dependency] private readonly ThievingSystem _thieving = default!;

public override void Initialize()
{
Expand Down Expand Up @@ -97,6 +98,8 @@ private void StartDoAfter(EntityUid user, EntityUid item, EntityUid wearer, Togg

var (time, stealth) = _strippable.GetStripTimeModifiers(user, wearer, component.StripDelay.Value);

bool hidden = (stealth == ThievingStealth.Hidden);

var args = new DoAfterArgs(EntityManager, user, time, new ToggleClothingDoAfterEvent(), item, wearer, item)
{
BreakOnDamage = true,
Expand All @@ -110,11 +113,8 @@ private void StartDoAfter(EntityUid user, EntityUid item, EntityUid wearer, Togg
if (!_doAfter.TryStartDoAfter(args))
return;

if (!stealth)
{
var popup = Loc.GetString("strippable-component-alert-owner-interact", ("user", Identity.Entity(user, EntityManager)), ("item", item));
_popupSystem.PopupEntity(popup, wearer, wearer, PopupType.Large);
}
if (!hidden)
_strippable.StripPopup("strippable-component-alert-owner-interact", stealth, wearer, user: Identity.Entity(user, EntityManager), item: item);
}

private void OnGetAttachedStripVerbsEvent(EntityUid uid, AttachedClothingComponent component, GetVerbsEvent<EquipmentVerb> args)
Expand Down
8 changes: 4 additions & 4 deletions Content.Shared/Strip/Components/StrippableComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ public sealed class StrippingSlotButtonPressed(string slot, bool isHand) : Bound
public sealed class StrippingEnsnareButtonPressed : BoundUserInterfaceMessage;

[ByRefEvent]
public abstract class BaseBeforeStripEvent(TimeSpan initialTime, bool stealth = false) : EntityEventArgs, IInventoryRelayEvent
public abstract class BaseBeforeStripEvent(TimeSpan initialTime, ThievingStealth stealth = ThievingStealth.Obvious) : EntityEventArgs, IInventoryRelayEvent
{
public readonly TimeSpan InitialTime = initialTime;
public float Multiplier = 1f;
public TimeSpan Additive = TimeSpan.Zero;
public bool Stealth = stealth;
public ThievingStealth Stealth = stealth;

public TimeSpan Time => TimeSpan.FromSeconds(MathF.Max(InitialTime.Seconds * Multiplier + Additive.Seconds, 0f));

Expand All @@ -51,7 +51,7 @@ public abstract class BaseBeforeStripEvent(TimeSpan initialTime, bool stealth =
/// This is also used by some stripping related interactions, i.e., interactions with items that are currently equipped by another player.
/// </remarks>
[ByRefEvent]
public sealed class BeforeStripEvent(TimeSpan initialTime, bool stealth = false) : BaseBeforeStripEvent(initialTime, stealth);
public sealed class BeforeStripEvent(TimeSpan initialTime, ThievingStealth stealth = ThievingStealth.Obvious) : BaseBeforeStripEvent(initialTime, stealth);

/// <summary>
/// Used to modify strip times. Raised directed at the target.
Expand All @@ -60,7 +60,7 @@ public sealed class BeforeStripEvent(TimeSpan initialTime, bool stealth = false)
/// This is also used by some stripping related interactions, i.e., interactions with items that are currently equipped by another player.
/// </remarks>
[ByRefEvent]
public sealed class BeforeGettingStrippedEvent(TimeSpan initialTime, bool stealth = false) : BaseBeforeStripEvent(initialTime, stealth);
public sealed class BeforeGettingStrippedEvent(TimeSpan initialTime, ThievingStealth stealth = ThievingStealth.Obvious) : BaseBeforeStripEvent(initialTime, stealth);

/// <summary>
/// Organizes the behavior of DoAfters for <see cref="StrippableSystem">.
Expand Down
20 changes: 15 additions & 5 deletions Content.Shared/Strip/Components/ThievingComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,24 @@ public sealed partial class ThievingComponent : Component
/// <summary>
/// How much the strip time should be shortened by
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("stripTimeReduction")]
[DataField]
public TimeSpan StripTimeReduction = TimeSpan.FromSeconds(0.5f);

/// <summary>
/// A multiplier coefficient for strip time
/// </summary>
[DataField]
public float StripTimeMultiplier = 1f;

/// <summary>
/// Should it notify the user if they're stripping a pocket?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("stealthy")]
public bool Stealthy;
[DataField]
public ThievingStealth Stealth = ThievingStealth.Hidden;

/// <summary>
/// Should the user be able to see hidden items? (i.e pockets)
/// </summary>
[DataField]
public bool IgnoreStripHidden;
}
18 changes: 17 additions & 1 deletion Content.Shared/Strip/SharedStrippableSystem.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using Content.Shared.DragDrop;
using Content.Shared.Hands.Components;
using Content.Shared.Popups;
using Content.Shared.Strip.Components;

namespace Content.Shared.Strip;

public abstract class SharedStrippableSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly ThievingSystem _thieving = default!;
public override void Initialize()
{
base.Initialize();
Expand All @@ -14,7 +17,7 @@ public override void Initialize()
SubscribeLocalEvent<StrippableComponent, DragDropDraggedEvent>(OnDragDrop);
}

public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid target, TimeSpan initialTime)
public (TimeSpan Time, ThievingStealth Stealth) GetStripTimeModifiers(EntityUid user, EntityUid target, TimeSpan initialTime)
{
var userEv = new BeforeStripEvent(initialTime);
RaiseLocalEvent(user, ref userEv);
Expand Down Expand Up @@ -55,4 +58,17 @@ private void OnCanDrop(EntityUid uid, StrippableComponent component, ref CanDrop
if (args.CanDrop)
args.Handled = true;
}

public void StripPopup(string messageId, ThievingStealth stealth, EntityUid target, EntityUid? user = null, EntityUid? item = null, string slot = "")
{
bool subtle = stealth == ThievingStealth.Subtle;
PopupType? popupSize = _thieving.GetPopupTypeFromStealth(stealth);

if (popupSize.HasValue) // We should always have a value if we're not hidden
_popup.PopupEntity(Loc.GetString(messageId,
("user", subtle ? Loc.GetString("thieving-component-user") : user ?? EntityUid.Invalid),
("item", subtle ? Loc.GetString("thieving-component-item") : item ?? EntityUid.Invalid),
("slot", slot)),
target, target, popupSize.Value);
}
}
39 changes: 37 additions & 2 deletions Content.Shared/Strip/ThievingSystem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Content.Shared.Inventory;
using Content.Shared.Strip;
using Content.Shared.Popups;
using Content.Shared.Strip.Components;
using Robust.Shared.Serialization;

namespace Content.Shared.Strip;

Expand All @@ -17,7 +18,41 @@ public override void Initialize()

private void OnBeforeStrip(EntityUid uid, ThievingComponent component, BeforeStripEvent args)
{
args.Stealth |= component.Stealthy;
args.Stealth = (ThievingStealth) Math.Max((sbyte) args.Stealth, (sbyte) component.Stealth);
args.Additive -= component.StripTimeReduction;
args.Multiplier *= component.StripTimeMultiplier;
}

public PopupType? GetPopupTypeFromStealth(ThievingStealth stealth)
{
switch (stealth)
{
case ThievingStealth.Hidden:
return null;

case ThievingStealth.Subtle:
return PopupType.Small;

default:
return PopupType.Large;
}
}
}
[Serializable, NetSerializable]
public enum ThievingStealth : sbyte
{
/// <summary>
/// Target sees a large popup indicating that an item is being stolen by who
/// </summary>
Obvious = 0,

/// <summary>
/// Target sees a small popup indicating that an item is being stolen
/// </summary>
Subtle = 1,

/// <summary>
/// Target does not see any popup regarding the stealing of an item
/// </summary>
Hidden = 2
}
6 changes: 5 additions & 1 deletion Resources/Locale/en-US/strip/strippable-component.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ strip-verb-get-data-text = Strip
## UI

strippable-bound-user-interface-stripping-menu-title = {$ownerName}'s inventory
strippable-bound-user-interface-stripping-menu-ensnare-button = Remove Leg Restraints
strippable-bound-user-interface-stripping-menu-ensnare-button = Remove Leg Restraints
# Stealth
thieving-component-user = Someone
thieving-component-item = something
5 changes: 5 additions & 0 deletions Resources/Locale/en-US/traits/traits.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ trait-description-SocialAnxiety = You are anxious when you speak and stutter.
trait-name-Snoring = Snoring
trait-description-Snoring = You will snore while sleeping.
trait-name-Thieving = Thieving
trait-description-Thieving =
You are deft with your hands, and talented at convincing people of their belongings.
You can identify pocketed items, steal them quieter, and steal ~33% faster.
Loading

0 comments on commit e06045c

Please sign in to comment.