2023-08-31 11:56:13 +10:00
using Content.Shared.Buckle ;
2024-01-21 01:32:47 -05:00
using Content.Shared.Buckle.Components ;
2025-04-19 04:17:13 +02:00
using Content.Shared.Construction.EntitySystems ;
using Content.Shared.Popups ;
2023-08-31 11:56:13 +10:00
using Content.Shared.Storage.Components ;
using Content.Shared.Verbs ;
using Robust.Shared.Containers ;
2025-04-19 04:17:13 +02:00
using Robust.Shared.Physics.Components ;
2023-08-31 11:56:13 +10:00
using Robust.Shared.Serialization ;
using Robust.Shared.Utility ;
namespace Content.Shared.Foldable ;
2025-04-16 13:54:42 +02:00
// TODO: This system could arguably be refactored into a general state system, as it is being utilized for a lot of different objects with various needs.
2023-08-31 11:56:13 +10:00
public sealed class FoldableSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
[Dependency] private readonly SharedBuckleSystem _buckle = default ! ;
[Dependency] private readonly SharedContainerSystem _container = default ! ;
2025-04-19 04:17:13 +02:00
[Dependency] private readonly AnchorableSystem _anchorable = default ! ;
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
2023-08-31 11:56:13 +10:00
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < FoldableComponent , GetVerbsEvent < AlternativeVerb > > ( AddFoldVerb ) ;
2024-02-03 19:52:44 -05:00
SubscribeLocalEvent < FoldableComponent , AfterAutoHandleStateEvent > ( OnHandleState ) ;
2023-08-31 11:56:13 +10:00
SubscribeLocalEvent < FoldableComponent , ComponentInit > ( OnFoldableInit ) ;
SubscribeLocalEvent < FoldableComponent , ContainerGettingInsertedAttemptEvent > ( OnInsertEvent ) ;
SubscribeLocalEvent < FoldableComponent , StorageOpenAttemptEvent > ( OnFoldableOpenAttempt ) ;
2025-05-03 21:19:32 +02:00
SubscribeLocalEvent < FoldableComponent , EntityStorageInsertedIntoAttemptEvent > ( OnEntityStorageAttemptInsert ) ;
2024-01-21 01:32:47 -05:00
2024-06-20 03:14:18 +12:00
SubscribeLocalEvent < FoldableComponent , StrapAttemptEvent > ( OnStrapAttempt ) ;
2023-08-31 11:56:13 +10:00
}
2024-02-03 19:52:44 -05:00
private void OnHandleState ( EntityUid uid , FoldableComponent component , ref AfterAutoHandleStateEvent args )
2023-08-31 11:56:13 +10:00
{
2024-02-03 19:52:44 -05:00
SetFolded ( uid , component , component . IsFolded ) ;
2023-08-31 11:56:13 +10:00
}
private void OnFoldableInit ( EntityUid uid , FoldableComponent component , ComponentInit args )
{
SetFolded ( uid , component , component . IsFolded ) ;
}
private void OnFoldableOpenAttempt ( EntityUid uid , FoldableComponent component , ref StorageOpenAttemptEvent args )
{
if ( component . IsFolded )
args . Cancelled = true ;
}
2024-06-20 03:14:18 +12:00
public void OnStrapAttempt ( EntityUid uid , FoldableComponent comp , ref StrapAttemptEvent args )
2024-01-21 01:32:47 -05:00
{
2024-06-20 03:14:18 +12:00
if ( comp . IsFolded )
2024-01-21 01:32:47 -05:00
args . Cancelled = true ;
}
2025-05-03 21:19:32 +02:00
private void OnEntityStorageAttemptInsert ( Entity < FoldableComponent > entity ,
ref EntityStorageInsertedIntoAttemptEvent args )
{
if ( entity . Comp . IsFolded )
args . Cancelled = true ;
}
2023-08-31 11:56:13 +10:00
/// <summary>
/// Returns false if the entity isn't foldable.
/// </summary>
public bool IsFolded ( EntityUid uid , FoldableComponent ? component = null )
{
if ( ! Resolve ( uid , ref component ) )
return false ;
return component . IsFolded ;
}
/// <summary>
/// Set the folded state of the given <see cref="FoldableComponent"/>
/// </summary>
public void SetFolded ( EntityUid uid , FoldableComponent component , bool folded )
{
component . IsFolded = folded ;
Dirty ( uid , component ) ;
_appearance . SetData ( uid , FoldedVisuals . State , folded ) ;
_buckle . StrapSetEnabled ( uid , ! component . IsFolded ) ;
2024-02-03 19:52:44 -05:00
var ev = new FoldedEvent ( folded ) ;
RaiseLocalEvent ( uid , ref ev ) ;
2023-08-31 11:56:13 +10:00
}
private void OnInsertEvent ( EntityUid uid , FoldableComponent component , ContainerGettingInsertedAttemptEvent args )
{
2024-02-03 19:52:44 -05:00
if ( ! component . IsFolded & & ! component . CanFoldInsideContainer )
2023-08-31 11:56:13 +10:00
args . Cancel ( ) ;
}
2025-04-19 04:17:13 +02:00
public bool TryToggleFold ( EntityUid uid , FoldableComponent comp , EntityUid ? folder = null )
2023-08-31 22:30:40 -04:00
{
2025-04-19 04:17:13 +02:00
var result = TrySetFolded ( uid , comp , ! comp . IsFolded ) ;
if ( ! result & & folder ! = null )
{
if ( comp . IsFolded )
_popup . PopupPredicted ( Loc . GetString ( "foldable-unfold-fail" , ( "object" , uid ) ) , uid , folder . Value ) ;
else
_popup . PopupPredicted ( Loc . GetString ( "foldable-fold-fail" , ( "object" , uid ) ) , uid , folder . Value ) ;
}
return result ;
2023-08-31 22:30:40 -04:00
}
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
public bool CanToggleFold ( EntityUid uid , FoldableComponent ? fold = null )
{
if ( ! Resolve ( uid , ref fold ) )
return false ;
2024-02-03 19:52:44 -05:00
// Can't un-fold in any container unless enabled (locker, hands, inventory, whatever).
if ( _container . IsEntityInContainer ( uid ) & & ! fold . CanFoldInsideContainer )
2023-08-31 22:30:40 -04:00
return false ;
2025-04-19 04:17:13 +02:00
if ( ! TryComp ( uid , out PhysicsComponent ? body ) | |
! _anchorable . TileFree ( Transform ( uid ) . Coordinates , body ) )
return false ;
2025-03-20 09:30:47 -04:00
var ev = new FoldAttemptEvent ( fold ) ;
2023-08-31 22:30:40 -04:00
RaiseLocalEvent ( uid , ref ev ) ;
return ! ev . Cancelled ;
}
/// <summary>
/// Try to fold/unfold
/// </summary>
public bool TrySetFolded ( EntityUid uid , FoldableComponent comp , bool state )
{
if ( state = = comp . IsFolded )
return false ;
if ( ! CanToggleFold ( uid , comp ) )
return false ;
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
SetFolded ( uid , comp , state ) ;
return true ;
}
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
#region Verb
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
private void AddFoldVerb ( EntityUid uid , FoldableComponent component , GetVerbsEvent < AlternativeVerb > args )
{
2025-04-19 04:17:13 +02:00
if ( ! args . CanAccess | | ! args . CanInteract | | args . Hands = = null )
2023-08-31 22:30:40 -04:00
return ;
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
AlternativeVerb verb = new ( )
2023-08-31 11:56:13 +10:00
{
2025-04-19 04:17:13 +02:00
Act = ( ) = > TryToggleFold ( uid , component , args . User ) ,
2024-02-10 03:44:19 -05:00
Text = component . IsFolded ? Loc . GetString ( component . UnfoldVerbText ) : Loc . GetString ( component . FoldVerbText ) ,
2023-08-31 22:30:40 -04:00
Icon = new SpriteSpecifier . Texture ( new ( "/Textures/Interface/VerbIcons/fold.svg.192dpi.png" ) ) ,
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
// If the object is unfolded and they click it, they want to fold it, if it's folded, they want to pick it up
Priority = component . IsFolded ? 0 : 2 ,
} ;
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
args . Verbs . Add ( verb ) ;
}
2023-08-31 11:56:13 +10:00
2023-08-31 22:30:40 -04:00
#endregion
2023-08-31 11:56:13 +10:00
[Serializable, NetSerializable]
public enum FoldedVisuals : byte
{
State
}
}
2023-08-31 22:30:40 -04:00
/// <summary>
/// Event raised on an entity to determine if it can be folded.
/// </summary>
/// <param name="Cancelled"></param>
[ByRefEvent]
2025-03-20 09:30:47 -04:00
public record struct FoldAttemptEvent ( FoldableComponent Comp , bool Cancelled = false ) ;
2024-02-03 19:52:44 -05:00
/// <summary>
/// Event raised on an entity after it has been folded.
/// </summary>
/// <param name="IsFolded"></param>
[ByRefEvent]
public readonly record struct FoldedEvent ( bool IsFolded ) ;