]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
emag refactor (#15181)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Wed, 19 Apr 2023 05:46:00 +0000 (05:46 +0000)
committerGitHub <noreply@github.com>
Wed, 19 Apr 2023 05:46:00 +0000 (22:46 -0700)
* limitedcharges stuff from emag

* changes except broken

* fix

* the

* move recharging to server, emag namespace -> charges

* the

* use resolve

* pro

webedit gaming

* the

* the

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
Content.Client/Charges/Systems/ChargesSystem.cs [new file with mode: 0644]
Content.Server/Charges/Components/AutoRechargeComponent.cs [new file with mode: 0644]
Content.Server/Charges/Systems/ChargesSystem.cs [new file with mode: 0644]
Content.Shared/Charges/Components/LimitedChargesComponent.cs [new file with mode: 0644]
Content.Shared/Charges/Systems/SharedChargesSystem.cs [new file with mode: 0644]
Content.Shared/Emag/Components/EmagComponent.cs
Content.Shared/Emag/Systems/EmagSystem.cs
Resources/Locale/en-US/emag/emag.ftl
Resources/Locale/en-US/limited-charges/limited-charges.ftl [new file with mode: 0644]
Resources/Prototypes/Entities/Objects/Tools/emag.yml

diff --git a/Content.Client/Charges/Systems/ChargesSystem.cs b/Content.Client/Charges/Systems/ChargesSystem.cs
new file mode 100644 (file)
index 0000000..9170ac5
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Charges.Systems;
+
+namespace Content.Client.Charges.Systems;
+
+public sealed class ChargesSystem : SharedChargesSystem { }
diff --git a/Content.Server/Charges/Components/AutoRechargeComponent.cs b/Content.Server/Charges/Components/AutoRechargeComponent.cs
new file mode 100644 (file)
index 0000000..6a64c15
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Server.Charges.Systems;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Charges.Components;
+
+/// <summary>
+/// Something with limited charges that can be recharged automatically.
+/// Requires LimitedChargesComponent to function.
+/// </summary>
+[RegisterComponent]
+[Access(typeof(ChargesSystem))]
+public sealed class AutoRechargeComponent : Component
+{
+    /// <summary>
+    /// The time it takes to regain a single charge
+    /// </summary>
+    [DataField("rechargeDuration"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan RechargeDuration = TimeSpan.FromSeconds(90);
+
+    /// <summary>
+    /// The time when the next charge will be added
+    /// </summary>
+    [DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
+    public TimeSpan NextChargeTime = TimeSpan.MaxValue;
+}
diff --git a/Content.Server/Charges/Systems/ChargesSystem.cs b/Content.Server/Charges/Systems/ChargesSystem.cs
new file mode 100644 (file)
index 0000000..82758f4
--- /dev/null
@@ -0,0 +1,63 @@
+using Content.Server.Charges.Components;
+using Content.Shared.Charges.Components;
+using Content.Shared.Charges.Systems;
+using Content.Shared.Examine;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Charges.Systems;
+
+public sealed class ChargesSystem : SharedChargesSystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<AutoRechargeComponent, EntityUnpausedEvent>(OnUnpaused);
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<LimitedChargesComponent, AutoRechargeComponent>();
+        while (query.MoveNext(out var uid, out var charges, out var recharge))
+        {
+            if (charges.Charges == charges.MaxCharges || _timing.CurTime < recharge.NextChargeTime)
+                continue;
+
+            AddCharges(uid, 1, charges);
+            recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
+        }
+    }
+
+    private void OnUnpaused(EntityUid uid, AutoRechargeComponent comp, ref EntityUnpausedEvent args)
+    {
+        comp.NextChargeTime += args.PausedTime;
+    }
+
+    protected override void OnExamine(EntityUid uid, LimitedChargesComponent comp, ExaminedEvent args)
+    {
+        base.OnExamine(uid, comp, args);
+
+        // only show the recharging info if it's not full
+        if (!args.IsInDetailsRange || comp.Charges == comp.MaxCharges || !TryComp<AutoRechargeComponent>(uid, out var recharge))
+            return;
+
+        var timeRemaining = Math.Round((recharge.NextChargeTime - _timing.CurTime).TotalSeconds);
+        args.PushMarkup(Loc.GetString("limited-charges-recharging", ("seconds", timeRemaining)));
+    }
+
+    public override void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+    {
+        if (!Resolve(uid, ref comp, false))
+            return;
+
+        var startRecharge = comp.Charges == comp.MaxCharges;
+        base.UseCharge(uid, comp);
+        // start the recharge time after first use at full charge
+        if (startRecharge && TryComp<AutoRechargeComponent>(uid, out var recharge))
+            recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration;
+    }
+}
diff --git a/Content.Shared/Charges/Components/LimitedChargesComponent.cs b/Content.Shared/Charges/Components/LimitedChargesComponent.cs
new file mode 100644 (file)
index 0000000..6973ffb
--- /dev/null
@@ -0,0 +1,24 @@
+using Content.Shared.Charges.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Charges.Components;
+
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedChargesSystem))]
+[AutoGenerateComponentState]
+public sealed partial class LimitedChargesComponent : Component
+{
+    /// <summary>
+    /// The maximum number of charges
+    /// </summary>
+    [DataField("maxCharges"), ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public int MaxCharges = 3;
+
+    /// <summary>
+    /// The current number of charges
+    /// </summary>
+    [DataField("charges"), ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public int Charges = 3;
+}
diff --git a/Content.Shared/Charges/Systems/SharedChargesSystem.cs b/Content.Shared/Charges/Systems/SharedChargesSystem.cs
new file mode 100644 (file)
index 0000000..be1526d
--- /dev/null
@@ -0,0 +1,62 @@
+using Content.Shared.Charges.Components;
+using Content.Shared.Examine;
+
+namespace Content.Shared.Charges.Systems;
+
+public abstract class SharedChargesSystem : EntitySystem
+{
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<LimitedChargesComponent, ExaminedEvent>(OnExamine);
+    }
+
+    protected virtual void OnExamine(EntityUid uid, LimitedChargesComponent comp, ExaminedEvent args)
+    {
+        if (!args.IsInDetailsRange)
+            return;
+
+        args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", comp.Charges)));
+        if (comp.Charges == comp.MaxCharges)
+        {
+            args.PushMarkup(Loc.GetString("limited-charges-max-charges"));
+            return;
+        }
+    }
+
+    /// <summary>
+    /// Tries to add a number of charges. If it over or underflows it will be clamped, wasting the extra charges.
+    /// </summary>
+    public void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null)
+    {
+        if (!Resolve(uid, ref comp, false))
+            return;
+
+        var old = comp.Charges;
+        comp.Charges = Math.Clamp(comp.Charges + change, 0, comp.MaxCharges);
+        if (comp.Charges != old)
+            Dirty(comp);
+    }
+
+    /// <summary>
+    /// Gets the limited charges component and returns true if there are no charges. Will return false if there is no limited charges component.
+    /// </summary>
+    public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null)
+    {
+        // can't be empty if there are no limited charges
+        if (!Resolve(uid, ref comp, false))
+            return false;
+
+        return comp.Charges <= 0;
+    }
+
+    /// <summary>
+    /// Uses a single charge. Must check IsEmpty beforehand to prevent using with 0 charge.
+    /// </summary>
+    public virtual void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null)
+    {
+        if (Resolve(uid, ref comp, false))
+            AddCharges(uid, -1, comp);
+    }
+}
index e696671aff1e0c61b5d5d0b26e6bb47f8ac18b42..235cf0c744485b7b824134256ffc8bdb465508bf 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Shared.Emag.Systems;
 using Content.Shared.Tag;
 using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 using Robust.Shared.Serialization;
 
@@ -9,62 +8,13 @@ namespace Content.Shared.Emag.Components;
 
 [Access(typeof(EmagSystem))]
 [RegisterComponent, NetworkedComponent]
-public sealed class EmagComponent : Component
+[AutoGenerateComponentState]
+public sealed partial class EmagComponent : Component
 {
-    /// <summary>
-    /// The maximum number of charges the emag can have
-    /// </summary>
-    [DataField("maxCharges"), ViewVariables(VVAccess.ReadWrite)]
-    public int MaxCharges = 3;
-
-    /// <summary>
-    /// The current number of charges on the emag
-    /// </summary>
-    [DataField("charges"), ViewVariables(VVAccess.ReadWrite)]
-    public int Charges = 3;
-
-    /// <summary>
-    /// Whether or not the emag automatically recharges over time.
-    /// </summary>
-    [DataField("autoRecharge"), ViewVariables(VVAccess.ReadWrite)]
-    public bool AutoRecharge = true;
-
-    /// <summary>
-    /// The time it takes to regain a single charge
-    /// </summary>
-    [DataField("rechargeDuration"), ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan RechargeDuration = TimeSpan.FromSeconds(90);
-
-    /// <summary>
-    /// The time when the next charge will be added
-    /// </summary>
-    [DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan NextChargeTime = TimeSpan.MaxValue;
-
     /// <summary>
     /// The tag that marks an entity as immune to emags
     /// </summary>
     [DataField("emagImmuneTag", customTypeSerializer: typeof(PrototypeIdSerializer<TagPrototype>)), ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
     public string EmagImmuneTag = "EmagImmune";
 }
-
-[Serializable, NetSerializable]
-public sealed class EmagComponentState : ComponentState
-{
-    public int MaxCharges;
-    public int Charges;
-    public bool AutoRecharge;
-    public TimeSpan RechargeTime;
-    public TimeSpan NextChargeTime;
-    public string EmagImmuneTag;
-
-    public EmagComponentState(int maxCharges, int charges, TimeSpan rechargeTime, TimeSpan nextChargeTime, string emagImmuneTag, bool autoRecharge)
-    {
-        MaxCharges = maxCharges;
-        Charges = charges;
-        RechargeTime = rechargeTime;
-        NextChargeTime = nextChargeTime;
-        EmagImmuneTag = emagImmuneTag;
-        AutoRecharge = autoRecharge;
-    }
-}
index dc1225577e532c3d5871027ee62000bfe458ffb3..7d304381550cb1d12ef07e2cb3d79fc5085eb75e 100644 (file)
 using Content.Shared.Administration.Logs;
+using Content.Shared.Charges.Components;
+using Content.Shared.Charges.Systems;
 using Content.Shared.Database;
 using Content.Shared.Emag.Components;
-using Content.Shared.Examine;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
 using Content.Shared.Tag;
-using Robust.Shared.GameStates;
 using Robust.Shared.Network;
 using Robust.Shared.Timing;
 
-namespace Content.Shared.Emag.Systems
+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
 {
-    /// 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
-    public sealed class EmagSystem : EntitySystem
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly SharedChargesSystem _charges = default!;
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly TagSystem _tag = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    public override void Initialize()
     {
-        [Dependency] private readonly IGameTiming _timing = default!;
-        [Dependency] private readonly INetManager _net = default!;
-        [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
-        [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
-        [Dependency] private readonly TagSystem _tagSystem = default!;
+        base.Initialize();
 
-        public override void Initialize()
-        {
-            base.Initialize();
-            SubscribeLocalEvent<EmagComponent, ExaminedEvent>(OnExamine);
-            SubscribeLocalEvent<EmagComponent, AfterInteractEvent>(OnAfterInteract);
-            SubscribeLocalEvent<EmagComponent, ComponentGetState>(OnGetState);
-            SubscribeLocalEvent<EmagComponent, ComponentHandleState>(OnHandleState);
-            SubscribeLocalEvent<EmagComponent, EntityUnpausedEvent>(OnUnpaused);
-        }
+        SubscribeLocalEvent<EmagComponent, AfterInteractEvent>(OnAfterInteract);
+    }
 
-        private void OnGetState(EntityUid uid, EmagComponent component, ref ComponentGetState args)
-        {
-            args.State = new EmagComponentState(component.MaxCharges, component.Charges, component.RechargeDuration,
-                component.NextChargeTime, component.EmagImmuneTag, component.AutoRecharge);
-        }
+    private void OnAfterInteract(EntityUid uid, EmagComponent comp, AfterInteractEvent args)
+    {
+        if (!args.CanReach || args.Target is not { } target)
+            return;
 
-        private void OnHandleState(EntityUid uid, EmagComponent component, ref ComponentHandleState args)
-        {
-            if (args.Current is not EmagComponentState state)
-                return;
-
-            component.MaxCharges = state.MaxCharges;
-            component.Charges = state.Charges;
-            component.RechargeDuration = state.RechargeTime;
-            component.NextChargeTime = state.NextChargeTime;
-            component.EmagImmuneTag = state.EmagImmuneTag;
-            component.AutoRecharge = state.AutoRecharge;
-        }
+        args.Handled = TryUseEmag(uid, args.User, target, comp);
+    }
 
-        private void OnUnpaused(EntityUid uid, EmagComponent component, ref EntityUnpausedEvent args)
-        {
-            component.NextChargeTime += args.PausedTime;
-        }
+    /// <summary>
+    /// Tries to use the emag on a target entity
+    /// </summary>
+    public bool TryUseEmag(EntityUid uid, EntityUid user, EntityUid target, EmagComponent? comp = null)
+    {
+        if (!Resolve(uid, ref comp, false))
+            return false;
 
-        private void OnExamine(EntityUid uid, EmagComponent component, ExaminedEvent args)
-        {
-            args.PushMarkup(Loc.GetString("emag-charges-remaining", ("charges", component.Charges)));
-            if (component.Charges == component.MaxCharges)
-            {
-                args.PushMarkup(Loc.GetString("emag-max-charges"));
-                return;
-            }
-            var timeRemaining = Math.Round((component.NextChargeTime - _timing.CurTime).TotalSeconds);
-            args.PushMarkup(Loc.GetString("emag-recharging", ("seconds", timeRemaining)));
-        }
+        if (_tag.HasTag(target, comp.EmagImmuneTag))
+            return false;
 
-        public override void Update(float frameTime)
+        TryComp<LimitedChargesComponent>(uid, out var charges);
+        if (_charges.IsEmpty(uid, charges))
         {
-            base.Update(frameTime);
-
-            foreach (var emag in EntityQuery<EmagComponent>())
-            {
-                if (!emag.AutoRecharge)
-                    continue;
-
-                if (emag.Charges == emag.MaxCharges)
-                    continue;
-
-                if (_timing.CurTime < emag.NextChargeTime)
-                    continue;
-
-                ChangeEmagCharge(emag.Owner, 1, true, emag);
-            }
+            if (_net.IsClient && _timing.IsFirstTimePredicted)
+                _popup.PopupEntity(Loc.GetString("emag-no-charges"), user, user);
+            return false;
         }
 
-        private void OnAfterInteract(EntityUid uid, EmagComponent component, AfterInteractEvent args)
-        {
-            if (!args.CanReach || args.Target is not { } target)
-                return;
-
-            args.Handled = TryUseEmag(uid, args.User, target, component);
-        }
+        var handled = DoEmagEffect(user, target);
+        if (!handled)
+            return false;
 
-        /// <summary>
-        /// Changes the charge on an emag.
-        /// </summary>
-        public bool ChangeEmagCharge(EntityUid uid, int change, bool resetTimer, EmagComponent? component = null)
+        // only do popup on client
+        if (_net.IsClient && _timing.IsFirstTimePredicted)
         {
-            if (!Resolve(uid, ref component))
-                return false;
-
-            if (component.Charges + change < 0 || component.Charges + change > component.MaxCharges)
-                return false;
-
-            if (resetTimer || component.Charges == component.MaxCharges)
-                component.NextChargeTime = _timing.CurTime + component.RechargeDuration;
-
-            component.Charges += change;
-            Dirty(component);
-            return true;
+            _popup.PopupEntity(Loc.GetString("emag-success", ("target", Identity.Entity(target, EntityManager))), user,
+                user, PopupType.Medium);
         }
 
-        /// <summary>
-        /// Tries to use the emag on a target entity
-        /// </summary>
-        public bool TryUseEmag(EntityUid emag, EntityUid user, EntityUid target, EmagComponent? component = null)
-        {
-            if (!Resolve(emag, ref component, false))
-                return false;
-
-            if (_tagSystem.HasTag(target, component.EmagImmuneTag))
-                return false;
-
-            if (component.Charges <= 0)
-            {
-                if (_net.IsServer)
-                    _popupSystem.PopupEntity(Loc.GetString("emag-no-charges"), user, user);
-                return false;
-            }
-
-            var handled = DoEmagEffect(user, target);
-            if (!handled)
-                return false;
+        _adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target}");
 
-            // only do popup on client
-            if (_net.IsClient && _timing.IsFirstTimePredicted)
-            {
-                _popupSystem.PopupEntity(Loc.GetString("emag-success", ("target", Identity.Entity(target, EntityManager))), user,
-                    user, PopupType.Medium);
-            }
-
-            _adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target}");
-
-            ChangeEmagCharge(emag, -1, false, component);
-            return true;
-        }
+        if (charges != null)
+            _charges.UseCharge(uid, charges);
+        return true;
+    }
 
-        /// <summary>
-        /// Does the emag effect on a specified entity
-        /// </summary>
-        public bool DoEmagEffect(EntityUid user, EntityUid target)
-        {
-            // prevent emagging twice
-            if (HasComp<EmaggedComponent>(target))
-                return false;
+    /// <summary>
+    /// Does the emag effect on a specified entity
+    /// </summary>
+    public bool DoEmagEffect(EntityUid user, EntityUid target)
+    {
+        // prevent emagging twice
+        if (HasComp<EmaggedComponent>(target))
+            return false;
 
-            var emaggedEvent = new GotEmaggedEvent(user);
-            RaiseLocalEvent(target, ref emaggedEvent);
+        var emaggedEvent = new GotEmaggedEvent(user);
+        RaiseLocalEvent(target, ref emaggedEvent);
 
-            if (emaggedEvent.Handled && !emaggedEvent.Repeatable)
-                EnsureComp<EmaggedComponent>(target);
-            return emaggedEvent.Handled;
-        }
+        if (emaggedEvent.Handled && !emaggedEvent.Repeatable)
+            EnsureComp<EmaggedComponent>(target);
+        return emaggedEvent.Handled;
     }
-
-    [ByRefEvent]
-    public record struct GotEmaggedEvent(EntityUid UserUid, bool Handled = false, bool Repeatable = false);
 }
+
+[ByRefEvent]
+public record struct GotEmaggedEvent(EntityUid UserUid, bool Handled = false, bool Repeatable = false);
index 7c9514cc1d8f2f1f4724c8357308b71fb7adc71e..b4679870b522f4c03e85b40a5327704f353eb982 100644 (file)
@@ -1,12 +1,2 @@
 emag-success = The card zaps something in {THE($target)}.
 emag-no-charges = No charges left!
-emag-charges-remaining = {$charges ->
-    [one] It has [color=fuchsia]{$charges}[/color] charge remaining.
-    *[other] It has [color=fuchsia]{$charges}[/color] charges remaining.
-}
-
-emag-max-charges = It's at [color=green]maximum[/color] charges.
-emag-recharging = {$seconds ->
-    [one] There is [color=yellow]{$seconds}[/color] second left until the next charge.
-    *[other] There are [color=yellow]{$seconds}[/color] seconds left until the next charge.
-}
\ No newline at end of file
diff --git a/Resources/Locale/en-US/limited-charges/limited-charges.ftl b/Resources/Locale/en-US/limited-charges/limited-charges.ftl
new file mode 100644 (file)
index 0000000..d6b28a0
--- /dev/null
@@ -0,0 +1,10 @@
+limited-charges-charges-remaining = {$charges ->
+    [one] It has [color=fuchsia]{$charges}[/color] charge remaining.
+    *[other] It has [color=fuchsia]{$charges}[/color] charges remaining.
+}
+
+limited-charges-max-charges = It's at [color=green]maximum[/color] charges.
+limited-charges-recharging = {$seconds ->
+    [one] There is [color=yellow]{$seconds}[/color] second left until the next charge.
+    *[other] There are [color=yellow]{$seconds}[/color] seconds left until the next charge.
+}
index bc120c4d0bfb1c500a66fa42ad390044e647d27e..dcc0d0a288b29f804a2f12afcee4cd028b41f83c 100644 (file)
@@ -5,6 +5,21 @@
   description: The all-in-one hacking solution. The thinking man's lockpick. The iconic EMAG.
   components:
   - type: Emag
+  - type: LimitedCharges
+  - type: AutoRecharge
+  - type: Sprite
+    netsync: false
+    sprite: Objects/Tools/emag.rsi
+    state: icon
+
+- type: entity
+  parent: BaseItem
+  id: EmagUnlimited
+  suffix: Unlimited
+  name: cryptographic sequencer
+  description: The all-in-one hacking solution. The thinking man's lockpick. The iconic EMAG.
+  components:
+  - type: Emag
   - type: Sprite
     netsync: false
     sprite: Objects/Tools/emag.rsi