Add clothing equipped/unequipped events (#27155)

* Added ClothingGotEquipped/ClothingGotUnequipped events

* Better version

* Implemented in a few places

* More implementations

* Add ClothingDidEquipped/ClothingDidUnequipped events

(cherry picked from commit 50631f430d62fc413bc8757f8d1e1c4523417816)
This commit is contained in:
Tayrtahn 2024-04-21 11:00:50 -04:00 committed by NullWanderer
parent 37189537b0
commit ab2d7626b3
No known key found for this signature in database
GPG Key ID: 212F05528FD678BE
13 changed files with 113 additions and 114 deletions

View File

@ -1,7 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Shared.Alert;
using Content.Shared.Clothing;
using Content.Shared.Inventory.Events;
namespace Content.Server.Clothing;
@ -13,8 +12,8 @@ public sealed class MagbootsSystem : SharedMagbootsSystem
{
base.Initialize();
SubscribeLocalEvent<MagbootsComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<MagbootsComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
}
protected override void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
@ -38,19 +37,13 @@ public sealed class MagbootsSystem : SharedMagbootsSystem
}
}
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, GotUnequippedEvent args)
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args)
{
if (args.Slot == "shoes")
{
UpdateMagbootEffects(args.Equipee, uid, false, component);
}
UpdateMagbootEffects(args.Wearer, uid, false, component);
}
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, GotEquippedEvent args)
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args)
{
if (args.Slot == "shoes")
{
UpdateMagbootEffects(args.Equipee, uid, true, component);
}
UpdateMagbootEffects(args.Wearer, uid, true, component);
}
}

View File

@ -1,16 +1,14 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Fluids.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Database;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory.Events;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Spillable;
@ -29,8 +27,8 @@ public sealed partial class PuddleSystem
SubscribeLocalEvent<SpillableComponent, LandEvent>(SpillOnLand);
// Openable handles the event if it's closed
SubscribeLocalEvent<SpillableComponent, MeleeHitEvent>(SplashOnMeleeHit, after: [typeof(OpenableSystem)]);
SubscribeLocalEvent<SpillableComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SpillableComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<SpillableComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SpillableComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<SpillableComponent, SolutionContainerOverflowEvent>(OnOverflow);
SubscribeLocalEvent<SpillableComponent, SpillDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<SpillableComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
@ -99,20 +97,11 @@ public sealed partial class PuddleSystem
}
}
private void OnGotEquipped(Entity<SpillableComponent> entity, ref GotEquippedEvent args)
private void OnGotEquipped(Entity<SpillableComponent> entity, ref ClothingGotEquippedEvent args)
{
if (!entity.Comp.SpillWorn)
return;
if (!TryComp(entity, out ClothingComponent? clothing))
return;
// check if entity was actually used as clothing
// not just taken in pockets or something
var isCorrectSlot = clothing.Slots.HasFlag(args.SlotFlags);
if (!isCorrectSlot)
return;
if (!_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, out var solution))
return;
@ -124,10 +113,10 @@ public sealed partial class PuddleSystem
// spill all solution on the player
var drainedSolution = _solutionContainerSystem.Drain(entity.Owner, soln.Value, solution.Volume);
TrySplashSpillAt(entity.Owner, Transform(args.Equipee).Coordinates, drainedSolution, out _);
TrySplashSpillAt(entity.Owner, Transform(args.Wearer).Coordinates, drainedSolution, out _);
}
private void OnGotUnequipped(Entity<SpillableComponent> entity, ref GotUnequippedEvent args)
private void OnGotUnequipped(Entity<SpillableComponent> entity, ref ClothingGotUnequippedEvent args)
{
if (!entity.Comp.SpillWorn)
return;

View File

@ -3,11 +3,10 @@ using Content.Server.Medical.Components;
using Content.Server.Medical.Stethoscope.Components;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory.Events;
using Content.Shared.Medical;
using Content.Shared.Medical.Stethoscope;
using Content.Shared.Mobs.Components;
@ -26,8 +25,8 @@ namespace Content.Server.Medical.Stethoscope
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StethoscopeComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<StethoscopeComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<StethoscopeComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<StethoscopeComponent, ClothingGotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<WearingStethoscopeComponent, GetVerbsEvent<InnateVerb>>(AddStethoscopeVerb);
SubscribeLocalEvent<StethoscopeComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<StethoscopeComponent, StethoscopeActionEvent>(OnStethoscopeAction);
@ -37,26 +36,20 @@ namespace Content.Server.Medical.Stethoscope
/// <summary>
/// Add the component the verb event subs to if the equippee is wearing the stethoscope.
/// </summary>
private void OnEquipped(EntityUid uid, StethoscopeComponent component, GotEquippedEvent args)
private void OnEquipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotEquippedEvent args)
{
if (!TryComp<ClothingComponent>(uid, out var clothing))
return;
// Is the clothing in its actual slot?
if (!clothing.Slots.HasFlag(args.SlotFlags))
return;
component.IsActive = true;
var wearingComp = EnsureComp<WearingStethoscopeComponent>(args.Equipee);
var wearingComp = EnsureComp<WearingStethoscopeComponent>(args.Wearer);
wearingComp.Stethoscope = uid;
}
private void OnUnequipped(EntityUid uid, StethoscopeComponent component, GotUnequippedEvent args)
private void OnUnequipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotUnequippedEvent args)
{
if (!component.IsActive)
return;
RemComp<WearingStethoscopeComponent>(args.Equipee);
RemComp<WearingStethoscopeComponent>(args.Wearer);
component.IsActive = false;
}

View File

@ -7,10 +7,10 @@ using Content.Server.GameTicking;
using Content.Server.Medical.CrewMonitoring;
using Content.Server.Popups;
using Content.Server.Station.Systems;
using Content.Shared.Clothing;
using Content.Shared.Damage;
using Content.Shared.DeviceNetwork;
using Content.Shared.Examine;
using Content.Shared.Inventory.Events;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
@ -40,8 +40,8 @@ public sealed class SuitSensorSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SuitSensorComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<SuitSensorComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<SuitSensorComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<SuitSensorComponent, ClothingGotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb);
SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
@ -160,19 +160,13 @@ public sealed class SuitSensorSystem : EntitySystem
}
}
private void OnEquipped(EntityUid uid, SuitSensorComponent component, GotEquippedEvent args)
private void OnEquipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotEquippedEvent args)
{
if (args.Slot != component.ActivationSlot)
return;
component.User = args.Equipee;
component.User = args.Wearer;
}
private void OnUnequipped(EntityUid uid, SuitSensorComponent component, GotUnequippedEvent args)
private void OnUnequipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotUnequippedEvent args)
{
if (args.Slot != component.ActivationSlot)
return;
component.User = null;
}

View File

@ -1,6 +1,5 @@
using Content.Server.Speech.Components;
using Content.Shared.Clothing.Components;
using Content.Shared.Inventory.Events;
using Content.Shared.Clothing;
namespace Content.Server.Speech.EntitySystems;
@ -11,29 +10,20 @@ public sealed class AddAccentClothingSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AddAccentClothingComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<AddAccentClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<AddAccentClothingComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<AddAccentClothingComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
}
private void OnGotEquipped(EntityUid uid, AddAccentClothingComponent component, GotEquippedEvent args)
private void OnGotEquipped(EntityUid uid, AddAccentClothingComponent component, ref ClothingGotEquippedEvent args)
{
if (!TryComp(uid, out ClothingComponent? clothing))
return;
// check if entity was actually used as clothing
// not just taken in pockets or something
var isCorrectSlot = clothing.Slots.HasFlag(args.SlotFlags);
if (!isCorrectSlot)
return;
// does the user already has this accent?
var componentType = _componentFactory.GetRegistration(component.Accent).Type;
if (HasComp(args.Equipee, componentType))
if (HasComp(args.Wearer, componentType))
return;
// add accent to the user
var accentComponent = (Component) _componentFactory.GetComponent(componentType);
AddComp(args.Equipee, accentComponent);
AddComp(args.Wearer, accentComponent);
// snowflake case for replacement accent
if (accentComponent is ReplacementAccentComponent rep)
@ -42,16 +32,16 @@ public sealed class AddAccentClothingSystem : EntitySystem
component.IsActive = true;
}
private void OnGotUnequipped(EntityUid uid, AddAccentClothingComponent component, GotUnequippedEvent args)
private void OnGotUnequipped(EntityUid uid, AddAccentClothingComponent component, ref ClothingGotUnequippedEvent args)
{
if (!component.IsActive)
return;
// try to remove accent
var componentType = _componentFactory.GetRegistration(component.Accent).Type;
if (EntityManager.HasComponent(args.Equipee, componentType))
if (EntityManager.HasComponent(args.Wearer, componentType))
{
EntityManager.RemoveComponent(args.Equipee, componentType);
EntityManager.RemoveComponent(args.Wearer, componentType);
}
component.IsActive = false;

View File

@ -1,6 +1,6 @@
using Content.Server.Actions;
using Content.Shared.Clothing;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
namespace Content.Server.VoiceMask;
@ -12,13 +12,9 @@ public sealed partial class VoiceMaskSystem
private const string MaskSlot = "mask";
private void OnEquip(EntityUid uid, VoiceMaskerComponent component, GotEquippedEvent args)
private void OnEquip(EntityUid uid, VoiceMaskerComponent component, ClothingGotEquippedEvent args)
{
var user = args.Equipee;
// have to be wearing the mask to use it, duh.
if (!_inventory.TryGetSlotEntity(user, MaskSlot, out var maskEntity) || maskEntity != uid)
return;
var user = args.Wearer;
var comp = EnsureComp<VoiceMaskComponent>(user);
comp.VoiceName = component.LastSetName;
comp.SpeechVerb = component.LastSpeechVerb;
@ -26,9 +22,9 @@ public sealed partial class VoiceMaskSystem
_actions.AddAction(user, ref component.ActionEntity, component.Action, uid);
}
private void OnUnequip(EntityUid uid, VoiceMaskerComponent compnent, GotUnequippedEvent args)
private void OnUnequip(EntityUid uid, VoiceMaskerComponent compnent, ClothingGotUnequippedEvent args)
{
RemComp<VoiceMaskComponent>(args.Equipee);
RemComp<VoiceMaskComponent>(args.Wearer);
}
private VoiceMaskerComponent? TryGetMask(EntityUid user)

View File

@ -3,7 +3,6 @@ using Content.Server.Chat.Systems;
using Content.Server.Popups;
using Content.Shared.Clothing;
using Content.Shared.Database;
using Content.Shared.Inventory.Events;
using Content.Shared.Popups;
using Content.Shared.Preferences;
using Content.Shared.Speech;
@ -27,8 +26,8 @@ public sealed partial class VoiceMaskSystem : EntitySystem
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeNameMessage>(OnChangeName);
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeVerbMessage>(OnChangeVerb);
SubscribeLocalEvent<VoiceMaskComponent, WearerMaskToggledEvent>(OnMaskToggled);
SubscribeLocalEvent<VoiceMaskerComponent, GotEquippedEvent>(OnEquip);
SubscribeLocalEvent<VoiceMaskerComponent, GotUnequippedEvent>(OnUnequip);
SubscribeLocalEvent<VoiceMaskerComponent, ClothingGotEquippedEvent>(OnEquip);
SubscribeLocalEvent<VoiceMaskerComponent, ClothingGotUnequippedEvent>(OnUnequip);
SubscribeLocalEvent<VoiceMaskSetNameEvent>(OnSetName);
// SubscribeLocalEvent<VoiceMaskerComponent, GetVerbsEvent<AlternativeVerb>>(GetVerbs);
}

View File

@ -1,5 +1,4 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Inventory.Events;
using Content.Shared.Clothing;
namespace Content.Shared.Chat.TypingIndicator;
@ -17,25 +16,21 @@ public abstract class SharedTypingIndicatorSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TypingIndicatorClothingComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<TypingIndicatorClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<TypingIndicatorClothingComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<TypingIndicatorClothingComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
}
private void OnGotEquipped(EntityUid uid, TypingIndicatorClothingComponent component, GotEquippedEvent args)
private void OnGotEquipped(EntityUid uid, TypingIndicatorClothingComponent component, ClothingGotEquippedEvent args)
{
if (!TryComp<ClothingComponent>(uid, out var clothing) ||
!TryComp<TypingIndicatorComponent>(args.Equipee, out var indicator))
if (!TryComp<TypingIndicatorComponent>(args.Wearer, out var indicator))
return;
var isCorrectSlot = clothing.Slots.HasFlag(args.SlotFlags);
if (!isCorrectSlot) return;
indicator.Prototype = component.Prototype;
}
private void OnGotUnequipped(EntityUid uid, TypingIndicatorClothingComponent component, GotUnequippedEvent args)
private void OnGotUnequipped(EntityUid uid, TypingIndicatorClothingComponent component, ClothingGotUnequippedEvent args)
{
if (!TryComp<TypingIndicatorComponent>(args.Equipee, out var indicator))
if (!TryComp<TypingIndicatorComponent>(args.Wearer, out var indicator))
return;
indicator.Prototype = SharedTypingIndicatorSystem.InitialIndicatorId;

View File

@ -1,5 +1,6 @@
using Content.Shared.Actions;
using Content.Shared.Clothing.Components;
namespace Content.Shared.Clothing;
@ -71,3 +72,31 @@ public readonly record struct ItemMaskToggledEvent(EntityUid Wearer, string? equ
/// </summary>
[ByRefEvent]
public readonly record struct WearerMaskToggledEvent(bool IsToggled);
/// <summary>
/// Raised on the clothing entity when it is equipped to a valid slot,
/// as determined by <see cref="ClothingComponent.Slots"/>.
/// </summary>
[ByRefEvent]
public readonly record struct ClothingGotEquippedEvent(EntityUid Wearer, ClothingComponent Clothing);
/// <summary>
/// Raised on the clothing entity when it is unequipped from a valid slot,
/// as determined by <see cref="ClothingComponent.Slots"/>.
/// </summary>
[ByRefEvent]
public readonly record struct ClothingGotUnequippedEvent(EntityUid Wearer, ClothingComponent Clothing);
/// <summary>
/// Raised on an entity when they equip a clothing item to a valid slot,
/// as determined by <see cref="ClothingComponent.Slots"/>.
/// </summary>
[ByRefEvent]
public readonly record struct ClothingDidEquippedEvent(Entity<ClothingComponent> Clothing);
/// <summary>
/// Raised on an entity when they unequip a clothing item from a valid slot,
/// as determined by <see cref="ClothingComponent.Slots"/>.
/// </summary>
[ByRefEvent]
public readonly record struct ClothingDidUnequippedEvent(Entity<ClothingComponent> Clothing);

View File

@ -66,6 +66,9 @@ public sealed partial class ClothingComponent : Component
[DataField("unisexMask")]
public ClothingMask UnisexMask = ClothingMask.UniformFull;
/// <summary>
/// Name of the inventory slot the clothing is in.
/// </summary>
public string? InSlot;
[DataField, ViewVariables(VVAccess.ReadWrite)]

View File

@ -120,10 +120,28 @@ public abstract class ClothingSystem : EntitySystem
{
component.InSlot = args.Slot;
CheckEquipmentForLayerHide(args.Equipment, args.Equipee);
if ((component.Slots & args.SlotFlags) != SlotFlags.NONE)
{
var gotEquippedEvent = new ClothingGotEquippedEvent(args.Equipee, component);
RaiseLocalEvent(uid, ref gotEquippedEvent);
var didEquippedEvent = new ClothingDidEquippedEvent((uid, component));
RaiseLocalEvent(args.Equipee, ref didEquippedEvent);
}
}
protected virtual void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args)
{
if ((component.Slots & args.SlotFlags) != SlotFlags.NONE)
{
var gotUnequippedEvent = new ClothingGotUnequippedEvent(args.Equipee, component);
RaiseLocalEvent(uid, ref gotUnequippedEvent);
var didUnequippedEvent = new ClothingDidUnequippedEvent((uid, component));
RaiseLocalEvent(args.Equipee, ref didUnequippedEvent);
}
component.InSlot = null;
CheckEquipmentForLayerHide(args.Equipment, args.Equipee);
}

View File

@ -17,34 +17,28 @@ public sealed class SkatesSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<SkatesComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SkatesComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<SkatesComponent, ClothingGotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SkatesComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
}
/// <summary>
/// When item is unequipped from the shoe slot, friction, aceleration and collide on impact return to default settings.
/// </summary>
public void OnGotUnequipped(EntityUid uid, SkatesComponent component, GotUnequippedEvent args)
public void OnGotUnequipped(EntityUid uid, SkatesComponent component, ClothingGotUnequippedEvent args)
{
if (!TryComp(args.Equipee, out MovementSpeedModifierComponent? speedModifier))
if (!TryComp(args.Wearer, out MovementSpeedModifierComponent? speedModifier))
return;
if (args.Slot == "shoes")
{
_move.ChangeFriction(args.Equipee, MovementSpeedModifierComponent.DefaultFriction, MovementSpeedModifierComponent.DefaultFrictionNoInput, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier);
_impact.ChangeCollide(args.Equipee, component.DefaultMinimumSpeed, component.DefaultStunSeconds, component.DefaultDamageCooldown, component.DefaultSpeedDamage);
}
_move.ChangeFriction(args.Wearer, MovementSpeedModifierComponent.DefaultFriction, MovementSpeedModifierComponent.DefaultFrictionNoInput, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier);
_impact.ChangeCollide(args.Wearer, component.DefaultMinimumSpeed, component.DefaultStunSeconds, component.DefaultDamageCooldown, component.DefaultSpeedDamage);
}
/// <summary>
/// When item is equipped into the shoe slot, friction, acceleration and collide on impact are adjusted.
/// </summary>
private void OnGotEquipped(EntityUid uid, SkatesComponent component, GotEquippedEvent args)
private void OnGotEquipped(EntityUid uid, SkatesComponent component, ClothingGotEquippedEvent args)
{
if (args.Slot == "shoes")
{
_move.ChangeFriction(args.Equipee, component.Friction, component.FrictionNoInput, component.Acceleration);
_impact.ChangeCollide(args.Equipee, component.MinimumSpeed, component.StunSeconds, component.DamageCooldown, component.SpeedDamage);
}
_move.ChangeFriction(args.Wearer, component.Friction, component.FrictionNoInput, component.Acceleration);
_impact.ChangeCollide(args.Wearer, component.MinimumSpeed, component.StunSeconds, component.DamageCooldown, component.SpeedDamage);
}
}

View File

@ -22,12 +22,18 @@ public abstract class UnequippedEventBase : EntityEventArgs
/// </summary>
public readonly string SlotGroup;
/// <summary>
/// Slotflags of the slot the entity just got unequipped from.
/// </summary>
public readonly SlotFlags SlotFlags;
public UnequippedEventBase(EntityUid equipee, EntityUid equipment, SlotDefinition slotDefinition)
{
Equipee = equipee;
Equipment = equipment;
Slot = slotDefinition.Name;
SlotGroup = slotDefinition.SlotGroup;
SlotFlags = slotDefinition.SlotFlags;
}
}