Predict CardboardBoxSystem (#43994)

* CardboardBox-System-Predict

* review

* SubscribeLocalEvent

* Storage

* Apply suggestions from code review

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
Pok 2026-07-12 05:40:19 +03:00 • committed by GitHub
parent 0446d900d6
commit 68ec73bea6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 225 additions and 217 deletions

View file

@ -11,10 +11,11 @@ namespace Content.Client.CardboardBox;
public sealed partial class CardboardBoxSystem : SharedCardboardBoxSystem
{
[Dependency] private EntityLookupSystem _entityLookup = default!;
[Dependency] private TransformSystem _transform = default!;
[Dependency] private ExamineSystemShared _examine = default!;
[Dependency] private SpriteSystem _sprite = default!;
[Dependency] private EntityQuery<MobStateComponent> _mobStateQuery = default!;
[Dependency] private TransformSystem _transform = default!;
[Dependency] private EntityQuery<MobStateComponent> _mobStateQuery;
public override void Initialize()
{
@ -33,13 +34,13 @@ public sealed partial class CardboardBoxSystem : SharedCardboardBoxSystem
var xform = Transform(source);
var sourcePos = _transform.GetMapCoordinates(source, xform);
//Any mob that can move should be surprised?
//God mind rework needs to come faster so it can just check for mind
//TODO: Replace with Mind Query when mind rework is in.
// Any mob that can move should be surprised?
// God mind rework needs to come faster so it can just check for mind
// TODO: Replace with Mind Query when mind rework is in.
var mobMoverEntities = new List<EntityUid>();
var mover = GetEntity(msg.Mover);
//Filter out entities in range to see that they're a mob and add them to the mobMoverEntities hash for faster lookup
// Filter out entities in range to see that they're a mob and add them to the mobMoverEntities hash for faster lookup
var movers = new HashSet<Entity<MobMoverComponent>>();
_entityLookup.GetEntitiesInRange(xform.Coordinates, box.Distance, movers);
@ -52,7 +53,7 @@ public sealed partial class CardboardBoxSystem : SharedCardboardBoxSystem
mobMoverEntities.Add(uid);
}
//Play the effect for the mobs as long as they can see the box and are in range.
// Play the effect for the mobs as long as they can see the box and are in range.
foreach (var mob in mobMoverEntities)
{
var mapPos = _transform.GetMapCoordinates(mob);
@ -64,13 +65,11 @@ public sealed partial class CardboardBoxSystem : SharedCardboardBoxSystem
continue;
var ent = Spawn(box.Effect, mapPos);
if (!TryComp(ent, out TransformComponent? entTransform) || !TryComp<SpriteComponent>(ent, out var sprite))
continue;
_sprite.SetOffset((ent, sprite), new Vector2(0, 1));
_transform.SetParent(ent, entTransform, mob);
}
}
}

View file

@ -1,138 +1,5 @@
using Content.Server.Storage.EntitySystems;
using Content.Shared.Access.Components;
using Content.Shared.CardboardBox;
using Content.Shared.CardboardBox.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Interaction;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Stealth;
using Content.Shared.Stealth.Components;
using Content.Shared.Storage.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Timing;
namespace Content.Server.CardboardBox;
public sealed partial class CardboardBoxSystem : SharedCardboardBoxSystem
{
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private SharedMoverController _mover = default!;
[Dependency] private IGameTiming _timing = default!;
[Dependency] private SharedStealthSystem _stealth = default!;
[Dependency] private DamageableSystem _damageable = default!;
[Dependency] private EntityStorageSystem _storage = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CardboardBoxComponent, StorageAfterOpenEvent>(AfterStorageOpen);
SubscribeLocalEvent<CardboardBoxComponent, StorageBeforeOpenEvent>(BeforeStorageOpen);
SubscribeLocalEvent<CardboardBoxComponent, StorageAfterCloseEvent>(AfterStorageClosed);
SubscribeLocalEvent<CardboardBoxComponent, GetAdditionalAccessEvent>(OnGetAdditionalAccess);
SubscribeLocalEvent<CardboardBoxComponent, ActivateInWorldEvent>(OnInteracted);
SubscribeLocalEvent<CardboardBoxComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<CardboardBoxComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
SubscribeLocalEvent<CardboardBoxComponent, DamageChangedEvent>(OnDamage);
}
private void OnInteracted(EntityUid uid, CardboardBoxComponent component, ActivateInWorldEvent args)
{
if (args.Handled)
return;
if (!TryComp<EntityStorageComponent>(uid, out var box))
return;
if (!args.Complex)
{
if (box.Open || !box.Contents.Contains(args.User))
return;
}
args.Handled = true;
_storage.ToggleOpen(args.User, uid, box);
if (box.Contents.Contains(args.User) && !box.Open)
{
_mover.SetRelay(args.User, uid);
component.Mover = args.User;
}
}
private void OnGetAdditionalAccess(EntityUid uid, CardboardBoxComponent component, ref GetAdditionalAccessEvent args)
{
if (component.Mover == null)
return;
args.Entities.Add(component.Mover.Value);
}
private void BeforeStorageOpen(EntityUid uid, CardboardBoxComponent component, ref StorageBeforeOpenEvent args)
{
if (component.Quiet)
return;
//Play effect & sound
if (component.Mover != null)
{
if (_timing.CurTime > component.EffectCooldown)
{
RaiseNetworkEvent(new PlayBoxEffectMessage(GetNetEntity(uid), GetNetEntity(component.Mover.Value)));
_audio.PlayPvs(component.EffectSound, uid);
component.EffectCooldown = _timing.CurTime + component.CooldownDuration;
}
}
}
private void AfterStorageOpen(EntityUid uid, CardboardBoxComponent component, ref StorageAfterOpenEvent args)
{
// If this box has a stealth/chameleon effect, disable the stealth effect while the box is open.
_stealth.SetEnabled(uid, false);
}
private void AfterStorageClosed(EntityUid uid, CardboardBoxComponent component, ref StorageAfterCloseEvent args)
{
// If this box has a stealth/chameleon effect, enable the stealth effect.
if (TryComp(uid, out StealthComponent? stealth))
{
_stealth.SetVisibility(uid, stealth.MaxVisibility, stealth);
_stealth.SetEnabled(uid, true, stealth);
}
}
//Relay damage to the mover
private void OnDamage(EntityUid uid, CardboardBoxComponent component, DamageChangedEvent args)
{
if (args.DamageDelta == null || !args.DamageIncreased || component.Mover is not { } mover)
return;
_damageable.ChangeDamage(mover, args.DamageDelta, origin: args.Origin);
}
private void OnEntInserted(EntityUid uid, CardboardBoxComponent component, EntInsertedIntoContainerMessage args)
{
if (!TryComp(args.Entity, out MobMoverComponent? mover))
return;
if (component.Mover == null)
{
_mover.SetRelay(args.Entity, uid);
component.Mover = args.Entity;
}
}
/// <summary>
/// Through e.g. teleporting, it's possible for the mover to exit the box without opening it.
/// Handle those situations but don't play the sound.
/// </summary>
private void OnEntRemoved(EntityUid uid, CardboardBoxComponent component, EntRemovedFromContainerMessage args)
{
if (args.Entity != component.Mover)
return;
RemComp<RelayInputMoverComponent>(component.Mover.Value);
component.Mover = null;
}
}
public sealed partial class CardboardBoxSystem : SharedCardboardBoxSystem;

View file

@ -48,7 +48,6 @@ public sealed partial class RevenantSystem
[Dependency] private SharedMapSystem _mapSystem = default!;
[Dependency] private EntityQuery<TagComponent> _tagQuery = default!;
[Dependency] private EntityQuery<EntityStorageComponent> _entityStorageQuery = default!;
[Dependency] private EntityQuery<ItemComponent> _itemQuery = default!;
[Dependency] private EntityQuery<PoweredLightComponent> _poweredLightQuery = default!;
[Dependency] private EntityQuery<MobStateComponent> _mobStateQuery = default!;
@ -281,8 +280,7 @@ public sealed partial class RevenantSystem
continue;
//randomly opens some lockers and such.
if (_entityStorageQuery.TryGetComponent(ent, out var entstorecomp))
_entityStorage.OpenStorage(ent, entstorecomp);
_entityStorage.OpenStorage(ent, args.Performer);
//chucks shit
if (_itemQuery.HasComponent(ent) &&

View file

@ -86,7 +86,7 @@ public sealed partial class BluespaceLockerSystem : EntitySystem
// Close target if it is open
if (target.Value.storageComponent.Open)
_entityStorage.CloseStorage(target.Value.uid, target.Value.storageComponent);
_entityStorage.CloseStorage((target.Value.uid, target.Value.storageComponent));
// Apply bluespace effects if target is not a bluespace locker, otherwise let it handle it
if (target.Value.bluespaceLockerComponent == null)
@ -352,7 +352,7 @@ public sealed partial class BluespaceLockerSystem : EntitySystem
if (Resolve(target.Value.uid, ref lockComponent, false) && lockComponent.Locked)
_lockSystem.Unlock(target.Value.uid, target.Value.uid, lockComponent);
_entityStorage.OpenStorage(target.Value.uid, target.Value.storageComponent);
_entityStorage.OpenStorage((target.Value.uid, target.Value.storageComponent));
}
// Bluespace effects

View file

@ -1,72 +1,67 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.CardboardBox.Components;
/// <summary>
/// Allows a user to control an EntityStorage entity while inside of it.
/// Used for big cardboard box entities.
/// </summary>
[RegisterComponent, NetworkedComponent]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class CardboardBoxComponent : Component
{
/// <summary>
/// The person in control of this box
/// </summary>
[DataField("mover")]
[DataField, AutoNetworkedField]
public EntityUid? Mover;
/// <summary>
/// The entity used for the box opening effect
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("effect")]
public string Effect = "Exclamation";
[DataField]
public EntProtoId Effect = "Exclamation";
/// <summary>
/// Sound played upon effect creation
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("effectSound")]
[DataField]
public SoundSpecifier? EffectSound;
/// <summary>
/// Whether to prevent the box from making the sound and effect
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("quiet")]
public bool Quiet = false;
[DataField]
public bool Quiet;
/// <summary>
/// How far should the box opening effect go?
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("distance")]
[DataField]
public float Distance = 6f;
/// <summary>
/// Time at which the sound effect can next be played.
/// </summary>
[DataField("effectCooldown", customTypeSerializer: typeof(TimeOffsetSerializer))]
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
public TimeSpan EffectCooldown;
/// <summary>
/// Time between sound effects. Prevents effect spam
/// </summary>
[DataField("cooldownDuration")]
[DataField]
public TimeSpan CooldownDuration = TimeSpan.FromSeconds(5f);
}
/// <summary>
/// Message to play the box effect.
/// </summary>
[Serializable, NetSerializable]
public sealed class PlayBoxEffectMessage : EntityEventArgs
public sealed class PlayBoxEffectMessage(NetEntity source, NetEntity mover) : EntityEventArgs
{
public NetEntity Source;
public NetEntity Mover;
public PlayBoxEffectMessage(NetEntity source, NetEntity mover)
{
Source = source;
Mover = mover;
}
public NetEntity Source = source;
public NetEntity Mover = mover;
}

View file

@ -1,6 +1,156 @@
namespace Content.Shared.CardboardBox;
using System.Numerics;
using Content.Shared.Access.Components;
using Content.Shared.CardboardBox.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Interaction;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Stealth;
using Content.Shared.Stealth.Components;
using Content.Shared.Storage.Components;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
public abstract class SharedCardboardBoxSystem : EntitySystem
namespace Content.Shared.CardboardBox;
public abstract partial class SharedCardboardBoxSystem : EntitySystem
{
[Dependency] private IGameTiming _timing = default!;
[Dependency] private INetManager _net = default!;
[Dependency] private DamageableSystem _damageable = default!;
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private SharedEntityStorageSystem _storage = default!;
[Dependency] private SharedMoverController _mover = default!;
[Dependency] private SharedPhysicsSystem _physics = default!;
[Dependency] private SharedStealthSystem _stealth = default!;
[SubscribeLocalEvent]
private void OnInteracted(Entity<CardboardBoxComponent> ent, ref ActivateInWorldEvent args)
{
if (args.Handled)
return;
if (!TryComp<EntityStorageComponent>(ent, out var box))
return;
if (!args.Complex)
{
if (box.Open || !box.Contents.Contains(args.User))
return;
}
args.Handled = true;
_storage.ToggleOpen(args.User, ent, box);
if (!box.Contents.Contains(args.User) || box.Open)
return;
_mover.SetRelay(args.User, ent);
ent.Comp.Mover = args.User;
Dirty(ent);
}
[SubscribeLocalEvent]
private void OnGetAdditionalAccess(Entity<CardboardBoxComponent> ent, ref GetAdditionalAccessEvent args)
{
if (ent.Comp.Mover == null)
return;
args.Entities.Add(ent.Comp.Mover.Value);
}
[SubscribeLocalEvent]
private void BeforeStorageOpen(Entity<CardboardBoxComponent> ent, ref StorageBeforeOpenEvent args)
{
if (ent.Comp.Quiet)
return;
// Play effect & sound.
if (ent.Comp.Mover == null)
return;
if (_timing.CurTime <= ent.Comp.EffectCooldown)
return;
if (_net.IsServer)
RaiseNetworkEvent(new PlayBoxEffectMessage(GetNetEntity(ent), GetNetEntity(ent.Comp.Mover.Value)));
_audio.PlayPredicted(ent.Comp.EffectSound, ent, args.User);
ent.Comp.EffectCooldown = _timing.CurTime + ent.Comp.CooldownDuration;
Dirty(ent);
}
[SubscribeLocalEvent]
private void AfterStorageOpen(Entity<CardboardBoxComponent> ent, ref StorageAfterOpenEvent args)
{
// If this box has a stealth/chameleon effect, disable the stealth effect while the box is open.
if (!TryComp<StealthComponent>(ent, out var stealth))
return;
_stealth.SetEnabled(ent, false, stealth);
}
[SubscribeLocalEvent]
private void AfterStorageClosed(Entity<CardboardBoxComponent> ent, ref StorageAfterCloseEvent args)
{
// If this box has a stealth/chameleon effect, enable the stealth effect.
if (!TryComp<StealthComponent>(ent, out var stealth))
return;
_stealth.SetVisibility(ent, stealth.MaxVisibility, stealth);
_stealth.SetEnabled(ent, true, stealth);
}
/// <summary>
/// Relay damage to the mover.
/// </summary>
[SubscribeLocalEvent]
private void OnDamage(Entity<CardboardBoxComponent> ent, ref DamageDealtEvent args)
{
if (ent.Comp.Mover is not { } mover)
return;
_damageable.ChangeDamage(mover, args.Damage, origin: args.Origin);
}
[SubscribeLocalEvent]
private void OnEntInserted(Entity<CardboardBoxComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (_timing.ApplyingState)
return;
if (!HasComp<MobMoverComponent>(args.Entity))
return;
if (ent.Comp.Mover != null)
return;
_mover.SetRelay(args.Entity, ent);
ent.Comp.Mover = args.Entity;
Dirty(ent);
}
/// <summary>
/// Through e.g. teleporting, it's possible for the mover to exit the box without opening it.
/// Handle those situations but don't play the sound.
/// </summary>
[SubscribeLocalEvent]
private void OnEntRemoved(Entity<CardboardBoxComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (_timing.ApplyingState)
return;
if (args.Entity != ent.Comp.Mover)
return;
// Stops movement after exit.
_physics.SetLinearVelocity(ent, Vector2.Zero);
RemComp<RelayInputMoverComponent>(ent.Comp.Mover.Value);
ent.Comp.Mover = null;
Dirty(ent);
}
}

View file

@ -147,7 +147,7 @@ public abstract partial class SharedCrematoriumSystem : EntitySystem
PredictedTrySpawnInContainer(ent.Comp1.LeftOverProtoId, ent.Owner, ent.Comp2.Contents.ID, out _);
}
EntityStorage.OpenStorage(ent.Owner, ent.Comp2);
EntityStorage.OpenStorage((ent.Owner, ent.Comp2));
if (_net.IsServer) // can't predict without the user
_audio.PlayPvs(ent.Comp1.CremateFinishSound, ent.Owner);

View file

@ -60,7 +60,7 @@ public abstract partial class SharedGenpopSystem : EntitySystem
ent.Comp.LinkedId = EntityUid.Invalid;
_lock.Lock(ent.Owner, args.Actor);
_entityStorage.CloseStorage(ent);
_entityStorage.CloseStorage(ent.Owner, args.Actor);
CreateId(ent, args.Name, args.Sentence, args.Crime);
}
@ -200,7 +200,7 @@ public abstract partial class SharedGenpopSystem : EntitySystem
ent.Comp.LinkedId = null;
_lock.Unlock(ent.Owner, user);
_entityStorage.OpenStorage(ent.Owner);
_entityStorage.OpenStorage(ent.Owner, user);
if (TryComp<ExpireIdCardComponent>(ent.Comp.LinkedId, out var expire))
IdCard.ExpireId((ent.Comp.LinkedId.Value, expire));

View file

@ -184,16 +184,16 @@ public record struct EntityStorageIntoContainerAttemptEvent(BaseContainer Contai
public record struct StorageOpenAttemptEvent(EntityUid User, bool Silent, bool Cancelled = false);
[ByRefEvent]
public readonly record struct StorageBeforeOpenEvent;
public readonly record struct StorageBeforeOpenEvent(EntityUid? User);
[ByRefEvent]
public readonly record struct StorageAfterOpenEvent;
public readonly record struct StorageAfterOpenEvent(EntityUid? User);
[ByRefEvent]
public record struct StorageCloseAttemptEvent(EntityUid? User, bool Cancelled = false);
[ByRefEvent]
public readonly record struct StorageBeforeCloseEvent(HashSet<EntityUid> Contents, HashSet<EntityUid> BypassChecks);
public readonly record struct StorageBeforeCloseEvent(EntityUid? User, HashSet<EntityUid> Contents, HashSet<EntityUid> BypassChecks);
[ByRefEvent]
public readonly record struct StorageAfterCloseEvent;
public readonly record struct StorageAfterCloseEvent(EntityUid? User);

View file

@ -225,77 +225,76 @@ public abstract partial class SharedEntityStorageSystem : EntitySystem
}
}
public void OpenStorage(EntityUid uid, EntityStorageComponent? component = null)
public void OpenStorage(Entity<EntityStorageComponent?> target, EntityUid? user = null)
{
if (!Resolve(uid, ref component))
if (!Resolve(target, ref target.Comp))
return;
if (component.Open)
if (target.Comp.Open)
return;
var beforeev = new StorageBeforeOpenEvent();
RaiseLocalEvent(uid, ref beforeev);
component.Open = true;
Dirty(uid, component);
EmptyContents(uid, component);
ModifyComponents(uid, component);
if (_net.IsClient && _timing.IsFirstTimePredicted)
_audio.PlayPvs(component.OpenSound, uid);
ReleaseGas(uid, component);
var afterev = new StorageAfterOpenEvent();
RaiseLocalEvent(uid, ref afterev);
var beforeev = new StorageBeforeOpenEvent(user);
RaiseLocalEvent(target, ref beforeev);
target.Comp.Open = true;
Dirty(target);
EmptyContents(target, target.Comp);
ModifyComponents(target, target.Comp);
_audio.PlayLocal(target.Comp.OpenSound, target, user);
ReleaseGas(target, target.Comp);
var afterev = new StorageAfterOpenEvent(user);
RaiseLocalEvent(target, ref afterev);
}
public void CloseStorage(EntityUid uid, EntityStorageComponent? component = null)
public void CloseStorage(Entity<EntityStorageComponent?> target, EntityUid? user = null)
{
if (!Resolve(uid, ref component))
if (!Resolve(target, ref target.Comp))
return;
if (!component.Open)
if (!target.Comp.Open)
return;
// Prevent the container from closing if it is queued for deletion. This is so that the container-emptying
// behaviour of DestructionEventArgs is respected. This exists because malicious players were using
// destructible boxes to delete entities by having two players simultaneously destroy and close the box in
// the same tick.
if (EntityManager.IsQueuedForDeletion(uid))
if (EntityManager.IsQueuedForDeletion(target))
return;
component.Open = false;
Dirty(uid, component);
target.Comp.Open = false;
Dirty(target);
var entities = _lookup.GetEntitiesInRange(
new EntityCoordinates(uid, component.EnteringOffset),
component.EnteringRange,
new EntityCoordinates(target, target.Comp.EnteringOffset),
target.Comp.EnteringRange,
LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Sundries
);
// Don't insert the container into itself.
entities.Remove(uid);
entities.Remove(target);
var ev = new StorageBeforeCloseEvent(entities, []);
RaiseLocalEvent(uid, ref ev);
var ev = new StorageBeforeCloseEvent(user, entities, []);
RaiseLocalEvent(target, ref ev);
foreach (var entity in ev.Contents)
{
if (!ev.BypassChecks.Contains(entity) && !CanInsert(entity, uid, component))
if (!ev.BypassChecks.Contains(entity) && !CanInsert(entity, target, target.Comp))
continue;
if (!AddToContents(entity, uid, component))
if (!AddToContents(entity, target, target.Comp))
continue;
if (component.Contents.ContainedEntities.Count >= component.Capacity)
if (target.Comp.Contents.ContainedEntities.Count >= target.Comp.Capacity)
break;
}
if (LifeStage(uid) >= EntityLifeStage.MapInitialized) // stop mappers from serializing air in locker
TakeGas(uid, component);
ModifyComponents(uid, component);
if (_net.IsClient && _timing.IsFirstTimePredicted)
_audio.PlayPvs(component.CloseSound, uid);
if (LifeStage(target) >= EntityLifeStage.MapInitialized) // stop mappers from serializing air in locker
TakeGas(target, target.Comp);
var afterev = new StorageAfterCloseEvent();
RaiseLocalEvent(uid, ref afterev);
ModifyComponents(target, target.Comp);
_audio.PlayLocal(target.Comp.CloseSound, target, user);
var afterev = new StorageAfterCloseEvent(user);
RaiseLocalEvent(target, ref afterev);
}
public bool Insert(EntityUid toInsert, EntityUid container, EntityStorageComponent? component = null)
@ -388,7 +387,7 @@ public abstract partial class SharedEntityStorageSystem : EntitySystem
if (!CanOpen(user, target, silent))
return false;
OpenStorage(target);
OpenStorage(target, user);
return true;
}
@ -399,7 +398,7 @@ public abstract partial class SharedEntityStorageSystem : EntitySystem
return false;
}
CloseStorage(target);
CloseStorage(target, user);
return true;
}