emag refactor (#15181)

* 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>
This commit is contained in:
deltanedas
2023-04-19 05:46:00 +00:00
committed by GitHub
parent 8507bcc8a8
commit 6ddd8761a9
10 changed files with 292 additions and 222 deletions

View 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;
}
}

View File

@@ -1,175 +1,101 @@
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);
}
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 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;
}
private void OnUnpaused(EntityUid uid, EmagComponent component, ref EntityUnpausedEvent args)
{
component.NextChargeTime += args.PausedTime;
}
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)));
}
public override void Update(float frameTime)
{
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);
}
}
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);
}
/// <summary>
/// Changes the charge on an emag.
/// </summary>
public bool ChangeEmagCharge(EntityUid uid, int change, bool resetTimer, EmagComponent? component = null)
{
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;
}
/// <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;
// 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;
}
/// <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);
if (emaggedEvent.Handled && !emaggedEvent.Repeatable)
EnsureComp<EmaggedComponent>(target);
return emaggedEvent.Handled;
}
SubscribeLocalEvent<EmagComponent, AfterInteractEvent>(OnAfterInteract);
}
[ByRefEvent]
public record struct GotEmaggedEvent(EntityUid UserUid, bool Handled = false, bool Repeatable = false);
private void OnAfterInteract(EntityUid uid, EmagComponent comp, AfterInteractEvent args)
{
if (!args.CanReach || args.Target is not { } target)
return;
args.Handled = TryUseEmag(uid, args.User, target, comp);
}
/// <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;
if (_tag.HasTag(target, comp.EmagImmuneTag))
return false;
TryComp<LimitedChargesComponent>(uid, out var charges);
if (_charges.IsEmpty(uid, charges))
{
if (_net.IsClient && _timing.IsFirstTimePredicted)
_popup.PopupEntity(Loc.GetString("emag-no-charges"), user, user);
return false;
}
var handled = DoEmagEffect(user, target);
if (!handled)
return false;
// only do popup on client
if (_net.IsClient && _timing.IsFirstTimePredicted)
{
_popup.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}");
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;
var emaggedEvent = new GotEmaggedEvent(user);
RaiseLocalEvent(target, ref emaggedEvent);
if (emaggedEvent.Handled && !emaggedEvent.Repeatable)
EnsureComp<EmaggedComponent>(target);
return emaggedEvent.Handled;
}
}
[ByRefEvent]
public record struct GotEmaggedEvent(EntityUid UserUid, bool Handled = false, bool Repeatable = false);