2023-01-21 10:12:45 -05:00
using Content.Shared.Administration.Logs ;
2023-04-19 05:46:00 +00:00
using Content.Shared.Charges.Components ;
using Content.Shared.Charges.Systems ;
2023-01-21 10:12:45 -05:00
using Content.Shared.Database ;
using Content.Shared.Emag.Components ;
using Content.Shared.IdentityManagement ;
using Content.Shared.Interaction ;
using Content.Shared.Popups ;
using Content.Shared.Tag ;
2025-01-30 05:05:47 +01:00
using Robust.Shared.Audio.Systems ;
2025-01-30 14:15:01 +01:00
using Robust.Shared.Serialization ;
2023-01-21 10:12:45 -05:00
2023-04-19 05:46:00 +00:00
namespace Content.Shared.Emag.Systems ;
/// How to add an emag interaction:
/// 1. Go to the system for the component you want the interaction with
/// 2. Subscribe to the GotEmaggedEvent
/// 3. Have some check for if this actually needs to be emagged or is already emagged (to stop charge waste)
/// 4. Past the check, add all the effects you desire and HANDLE THE EVENT ARGUMENT so a charge is spent
/// 5. Optionally, set Repeatable on the event to true if you don't want the emagged component to be added
public sealed class EmagSystem : EntitySystem
2023-01-21 10:12:45 -05:00
{
2023-04-19 05:46:00 +00:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2025-04-18 13:45:48 +10:00
[Dependency] private readonly SharedChargesSystem _sharedCharges = default ! ;
2023-04-19 05:46:00 +00:00
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
[Dependency] private readonly TagSystem _tag = default ! ;
2025-01-30 05:05:47 +01:00
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
2023-04-19 05:46:00 +00:00
public override void Initialize ( )
2023-01-21 10:12:45 -05:00
{
2023-04-19 05:46:00 +00:00
base . Initialize ( ) ;
2023-01-21 10:12:45 -05:00
2023-04-19 05:46:00 +00:00
SubscribeLocalEvent < EmagComponent , AfterInteractEvent > ( OnAfterInteract ) ;
2025-01-30 05:05:47 +01:00
SubscribeLocalEvent < EmaggedComponent , OnAccessOverriderAccessUpdatedEvent > ( OnAccessOverriderAccessUpdated ) ;
2023-04-19 05:46:00 +00:00
}
2023-01-21 10:12:45 -05:00
2025-01-30 05:05:47 +01:00
private void OnAccessOverriderAccessUpdated ( Entity < EmaggedComponent > entity , ref OnAccessOverriderAccessUpdatedEvent args )
{
if ( ! CompareFlag ( entity . Comp . EmagType , EmagType . Access ) )
return ;
entity . Comp . EmagType & = ~ EmagType . Access ;
Dirty ( entity ) ;
}
2023-04-19 05:46:00 +00:00
private void OnAfterInteract ( EntityUid uid , EmagComponent comp , AfterInteractEvent args )
{
if ( ! args . CanReach | | args . Target is not { } target )
return ;
2023-01-21 10:12:45 -05:00
2025-01-30 05:05:47 +01:00
args . Handled = TryEmagEffect ( ( uid , comp ) , args . User , target ) ;
2023-04-19 05:46:00 +00:00
}
2023-01-21 10:12:45 -05:00
2023-04-19 05:46:00 +00:00
/// <summary>
2025-07-04 02:08:45 -03:00
/// Does the emag effect on a specified entity with a specified EmagType. The optional field customEmagType can be used to override the emag type defined in the component.
2023-04-19 05:46:00 +00:00
/// </summary>
2025-07-04 02:08:45 -03:00
public bool TryEmagEffect ( Entity < EmagComponent ? > ent , EntityUid user , EntityUid target , EmagType ? customEmagType = null )
2023-04-19 05:46:00 +00:00
{
2025-01-30 05:05:47 +01:00
if ( ! Resolve ( ent , ref ent . Comp , false ) )
2023-04-19 05:46:00 +00:00
return false ;
2023-01-21 10:12:45 -05:00
2025-01-30 05:05:47 +01:00
if ( _tag . HasTag ( target , ent . Comp . EmagImmuneTag ) )
2023-04-19 05:46:00 +00:00
return false ;
2023-01-21 10:12:45 -05:00
2025-04-18 13:45:48 +10:00
Entity < LimitedChargesComponent ? > chargesEnt = ent . Owner ;
if ( _sharedCharges . IsEmpty ( chargesEnt ) )
2023-01-21 10:12:45 -05:00
{
2023-09-10 07:20:27 +01:00
_popup . PopupClient ( Loc . GetString ( "emag-no-charges" ) , user , user ) ;
2023-04-19 05:46:00 +00:00
return false ;
2023-01-21 10:12:45 -05:00
}
2025-07-04 02:08:45 -03:00
var typeToUse = customEmagType ? ? ent . Comp . EmagType ;
var emaggedEvent = new GotEmaggedEvent ( user , typeToUse ) ;
2025-01-30 05:05:47 +01:00
RaiseLocalEvent ( target , ref emaggedEvent ) ;
if ( ! emaggedEvent . Handled )
2023-04-19 05:46:00 +00:00
return false ;
2023-01-21 10:12:45 -05:00
2025-01-30 05:05:47 +01:00
_popup . PopupPredicted ( Loc . GetString ( "emag-success" , ( "target" , Identity . Entity ( target , EntityManager ) ) ) , user , user , PopupType . Medium ) ;
2023-01-21 10:12:45 -05:00
2025-01-30 05:05:47 +01:00
_audio . PlayPredicted ( ent . Comp . EmagSound , ent , ent ) ;
2023-01-21 10:12:45 -05:00
2025-07-04 02:08:45 -03:00
_adminLogger . Add ( LogType . Emag , LogImpact . High , $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target} with flag(s): {typeToUse}" ) ;
2025-01-30 05:05:47 +01:00
2025-04-18 13:45:48 +10:00
if ( emaggedEvent . Handled )
_sharedCharges . TryUseCharge ( chargesEnt ) ;
2025-01-30 05:05:47 +01:00
if ( ! emaggedEvent . Repeatable )
{
EnsureComp < EmaggedComponent > ( target , out var emaggedComp ) ;
2025-07-04 02:08:45 -03:00
emaggedComp . EmagType | = typeToUse ;
2025-01-30 05:05:47 +01:00
Dirty ( target , emaggedComp ) ;
}
return emaggedEvent . Handled ;
2023-04-19 05:46:00 +00:00
}
2023-01-21 10:12:45 -05:00
2023-04-19 05:46:00 +00:00
/// <summary>
2025-01-30 05:05:47 +01:00
/// Checks whether an entity has the EmaggedComponent with a set flag.
2023-04-19 05:46:00 +00:00
/// </summary>
2025-01-30 05:05:47 +01:00
/// <param name="target">The target entity to check for the flag.</param>
/// <param name="flag">The EmagType flag to check for.</param>
/// <returns>True if entity has EmaggedComponent and the provided flag. False if the entity lacks EmaggedComponent or provided flag.</returns>
public bool CheckFlag ( EntityUid target , EmagType flag )
2023-04-19 05:46:00 +00:00
{
2025-01-30 05:05:47 +01:00
if ( ! TryComp < EmaggedComponent > ( target , out var comp ) )
2023-04-19 05:46:00 +00:00
return false ;
2023-02-19 01:03:06 +00:00
2025-01-30 05:05:47 +01:00
if ( ( comp . EmagType & flag ) = = flag )
return true ;
2024-01-10 23:01:38 +01:00
2025-01-30 05:05:47 +01:00
return false ;
}
2024-01-10 23:01:38 +01:00
2025-01-30 05:05:47 +01:00
/// <summary>
/// Compares a flag to the target.
/// </summary>
/// <param name="target">The target flag to check.</param>
/// <param name="flag">The flag to check for within the target.</param>
/// <returns>True if target contains flag. Otherwise false.</returns>
public bool CompareFlag ( EmagType target , EmagType flag )
{
if ( ( target & flag ) = = flag )
return true ;
2023-02-19 01:03:06 +00:00
2025-01-30 05:05:47 +01:00
return false ;
2023-01-21 10:12:45 -05:00
}
}
2023-04-19 05:46:00 +00:00
2025-01-30 05:05:47 +01:00
[Flags]
2025-01-30 14:15:01 +01:00
[Serializable, NetSerializable]
2025-07-04 02:08:45 -03:00
public enum EmagType
2025-01-30 05:05:47 +01:00
{
None = 0 ,
2025-07-04 02:08:45 -03:00
All = ~ None ,
2025-01-30 05:05:47 +01:00
Interaction = 1 < < 1 ,
Access = 1 < < 2
}
2024-11-20 09:32:50 +06:00
/// <summary>
/// Shows a popup to emag user (client side only!) and adds <see cref="EmaggedComponent"/> to the entity when handled
/// </summary>
/// <param name="UserUid">Emag user</param>
2025-01-30 05:05:47 +01:00
/// <param name="Type">The emag type to use</param>
2024-11-20 09:32:50 +06:00
/// <param name="Handled">Did the emagging succeed? Causes a user-only popup to show on client side</param>
/// <param name="Repeatable">Can the entity be emagged more than once? Prevents adding of <see cref="EmaggedComponent"/></param>
/// <remarks>Needs to be handled in shared/client, not just the server, to actually show the emagging popup</remarks>
2023-04-19 05:46:00 +00:00
[ByRefEvent]
2025-01-30 05:05:47 +01:00
public record struct GotEmaggedEvent ( EntityUid UserUid , EmagType Type , bool Handled = false , bool Repeatable = false ) ;