From 7d2ef2bd47b38f067e68c73bf9dd74801dfe9606 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 18 Apr 2025 13:45:48 +1000 Subject: [PATCH] Action charges refactor (#33993) * Action charges refactor - Fixes the slight godmoding of baseactioncomponent. - Gets back 1ms of server time. * chorg * Remove FrameUpdate * Fixes * More fixes * Combine * Fixes * Updates * weh * Last fixes * weh * Fix naughty * YAML fixes * This one too * Merge conflicts * This thing * Review * Fix this as well * Icon fix * weh * Review * Review * seamless * Review --- Content.Client/Actions/ActionsSystem.cs | 31 +-- Content.Client/Charges/ChargesSystem.cs | 52 ++++ .../Charges/Systems/ChargesSystem.cs | 5 - .../Systems/Actions/ActionUIController.cs | 6 +- .../Systems/Actions/Controls/ActionButton.cs | 22 +- Content.Server/Charges/ChargesSystem.cs | 8 + .../Components/AutoRechargeComponent.cs | 27 -- .../Charges/Systems/ChargesSystem.cs | 53 ---- Content.Server/Flash/FlashSystem.cs | 8 +- Content.Shared/Actions/BaseActionComponent.cs | 24 -- Content.Shared/Actions/SharedActionsSystem.cs | 148 +---------- .../Components/AutoRechargeComponent.cs | 19 ++ .../Components/LimitedChargesComponent.cs | 29 +- .../Charges/Systems/SharedChargesSystem.cs | 251 +++++++++++++----- Content.Shared/Emag/Systems/EmagSystem.cs | 10 +- Content.Shared/Magic/SpellbookSystem.cs | 10 +- .../Ninja/Systems/DashAbilitySystem.cs | 4 +- .../Ninja/Systems/EnergyKatanaSystem.cs | 1 + .../RCD/Components/RCDAmmoComponent.cs | 2 +- Content.Shared/RCD/Systems/RCDAmmoSystem.cs | 7 +- Content.Shared/RCD/Systems/RCDSystem.cs | 17 +- Resources/Prototypes/Actions/types.yml | 12 +- .../Entities/Objects/Misc/fluff_lights.yml | 1 - .../Entities/Objects/Tools/lantern.yml | 1 - .../Entities/Objects/Tools/tools.yml | 4 +- .../Entities/Objects/Weapons/Melee/sword.yml | 1 - .../Entities/Objects/Weapons/security.yml | 2 - Resources/Prototypes/Magic/animate_spell.yml | 3 +- .../Prototypes/Magic/projectile_spells.yml | 4 - Resources/Prototypes/Magic/staves.yml | 3 +- 30 files changed, 366 insertions(+), 399 deletions(-) create mode 100644 Content.Client/Charges/ChargesSystem.cs delete mode 100644 Content.Client/Charges/Systems/ChargesSystem.cs create mode 100644 Content.Server/Charges/ChargesSystem.cs delete mode 100644 Content.Server/Charges/Components/AutoRechargeComponent.cs delete mode 100644 Content.Server/Charges/Systems/ChargesSystem.cs create mode 100644 Content.Shared/Charges/Components/AutoRechargeComponent.cs diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 5f0a8e1f2f..0302739816 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -1,6 +1,7 @@ using System.IO; using System.Linq; using Content.Shared.Actions; +using Content.Shared.Charges.Systems; using JetBrains.Annotations; using Robust.Client.Player; using Robust.Shared.ContentPack; @@ -22,6 +23,7 @@ namespace Content.Client.Actions { public delegate void OnActionReplaced(EntityUid actionId); + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IResourceManager _resources = default!; [Dependency] private readonly ISerializationManager _serialization = default!; @@ -51,29 +53,6 @@ namespace Content.Client.Actions SubscribeLocalEvent(OnEntityWorldTargetHandleState); } - public override void FrameUpdate(float frameTime) - { - base.FrameUpdate(frameTime); - - var worldActionQuery = EntityQueryEnumerator(); - while (worldActionQuery.MoveNext(out var uid, out var action)) - { - UpdateAction(uid, action); - } - - var instantActionQuery = EntityQueryEnumerator(); - while (instantActionQuery.MoveNext(out var uid, out var action)) - { - UpdateAction(uid, action); - } - - var entityActionQuery = EntityQueryEnumerator(); - while (entityActionQuery.MoveNext(out var uid, out var action)) - { - UpdateAction(uid, action); - } - } - private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args) { if (args.Current is not InstantActionComponentState state) @@ -127,9 +106,6 @@ namespace Content.Client.Actions component.Toggled = state.Toggled; component.Cooldown = state.Cooldown; component.UseDelay = state.UseDelay; - component.Charges = state.Charges; - component.MaxCharges = state.MaxCharges; - component.RenewCharges = state.RenewCharges; component.Container = EnsureEntity(state.Container, uid); component.EntityIcon = EnsureEntity(state.EntityIcon, uid); component.CheckCanInteract = state.CheckCanInteract; @@ -152,7 +128,8 @@ namespace Content.Client.Actions if (!ResolveActionData(actionId, ref action)) return; - action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor; + // TODO: Decouple this. + action.IconColor = _sharedCharges.GetCurrentCharges(actionId.Value) == 0 ? action.DisabledIconColor : action.OriginalIconColor; base.UpdateAction(actionId, action); if (_playerManager.LocalEntity != action.AttachedEntity) diff --git a/Content.Client/Charges/ChargesSystem.cs b/Content.Client/Charges/ChargesSystem.cs new file mode 100644 index 0000000000..2c7e0536cd --- /dev/null +++ b/Content.Client/Charges/ChargesSystem.cs @@ -0,0 +1,52 @@ +using Content.Client.Actions; +using Content.Shared.Actions; +using Content.Shared.Charges.Components; +using Content.Shared.Charges.Systems; + +namespace Content.Client.Charges; + +public sealed class ChargesSystem : SharedChargesSystem +{ + [Dependency] private readonly ActionsSystem _actions = default!; + + private Dictionary _lastCharges = new(); + private Dictionary _tempLastCharges = new(); + + public override void Update(float frameTime) + { + // Technically this should probably be in frameupdate but no one will ever notice a tick of delay on this. + base.Update(frameTime); + + if (!_timing.IsFirstTimePredicted) + return; + + // Update recharging actions. Server doesn't actually care about this and it's a waste of performance, actions are immediate. + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var recharge, out var charges)) + { + BaseActionComponent? actionComp = null; + + if (!_actions.ResolveActionData(uid, ref actionComp, logError: false)) + continue; + + var current = GetCurrentCharges((uid, charges, recharge)); + + if (!_lastCharges.TryGetValue(uid, out var last) || current != last) + { + _actions.UpdateAction(uid, actionComp); + } + + _tempLastCharges[uid] = current; + } + + _lastCharges.Clear(); + + foreach (var (uid, value) in _tempLastCharges) + { + _lastCharges[uid] = value; + } + + _tempLastCharges.Clear(); + } +} diff --git a/Content.Client/Charges/Systems/ChargesSystem.cs b/Content.Client/Charges/Systems/ChargesSystem.cs deleted file mode 100644 index 9170ac5e94..0000000000 --- a/Content.Client/Charges/Systems/ChargesSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared.Charges.Systems; - -namespace Content.Client.Charges.Systems; - -public sealed class ChargesSystem : SharedChargesSystem { } diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index d020a71359..a4157517ce 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -12,6 +12,7 @@ using Content.Client.UserInterface.Systems.Actions.Widgets; using Content.Client.UserInterface.Systems.Actions.Windows; using Content.Client.UserInterface.Systems.Gameplay; using Content.Shared.Actions; +using Content.Shared.Charges.Systems; using Content.Shared.Input; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -42,9 +43,9 @@ public sealed class ActionUIController : UIController, IOnStateChanged _timing.CurTime) { // The user is targeting with this action, but it is not valid. Maybe mark this click as @@ -483,7 +483,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); _controller = controller; MouseFilter = MouseFilterMode.Pass; @@ -194,14 +200,22 @@ public sealed class ActionButton : Control, IEntityControl var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName)); var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription)); + FormattedMessage? chargesText = null; - if (_action is { Charges: not null }) + // TODO: Don't touch this use an event make callers able to add their own shit for actions or I kill you. + if (_entities.TryGetComponent(ActionId, out LimitedChargesComponent? actionCharges)) { - var charges = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {_action.Charges.Value.ToString()}/{_action.MaxCharges.ToString()}")); - return new ActionAlertTooltip(name, decr, charges: charges); + var charges = _sharedChargesSys.GetCurrentCharges((ActionId.Value, actionCharges, null)); + chargesText = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {charges.ToString()}/{actionCharges.MaxCharges}")); + + if (_entities.TryGetComponent(ActionId, out AutoRechargeComponent? autoRecharge)) + { + var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((ActionId.Value, actionCharges, autoRecharge)); + chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining}")); + } } - return new ActionAlertTooltip(name, decr); + return new ActionAlertTooltip(name, decr, charges: chargesText); } protected override void ControlFocusExited() diff --git a/Content.Server/Charges/ChargesSystem.cs b/Content.Server/Charges/ChargesSystem.cs new file mode 100644 index 0000000000..6883dcb03d --- /dev/null +++ b/Content.Server/Charges/ChargesSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Charges.Systems; + +namespace Content.Server.Charges; + +public sealed class ChargesSystem : SharedChargesSystem +{ + +} diff --git a/Content.Server/Charges/Components/AutoRechargeComponent.cs b/Content.Server/Charges/Components/AutoRechargeComponent.cs deleted file mode 100644 index 165b181dcb..0000000000 --- a/Content.Server/Charges/Components/AutoRechargeComponent.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Server.Charges.Systems; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Server.Charges.Components; - -/// -/// Something with limited charges that can be recharged automatically. -/// Requires LimitedChargesComponent to function. -/// -// TODO: no reason this cant be predicted and server system deleted -[RegisterComponent, AutoGenerateComponentPause] -[Access(typeof(ChargesSystem))] -public sealed partial class AutoRechargeComponent : Component -{ - /// - /// The time it takes to regain a single charge - /// - [DataField("rechargeDuration"), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan RechargeDuration = TimeSpan.FromSeconds(90); - - /// - /// The time when the next charge will be added - /// - [DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan NextChargeTime; -} diff --git a/Content.Server/Charges/Systems/ChargesSystem.cs b/Content.Server/Charges/Systems/ChargesSystem.cs deleted file mode 100644 index 974928ee4b..0000000000 --- a/Content.Server/Charges/Systems/ChargesSystem.cs +++ /dev/null @@ -1,53 +0,0 @@ -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 Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - 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; - } - } - - 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(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 AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null) - { - if (!Query.Resolve(uid, ref comp, false)) - return; - - var startRecharge = comp.Charges == comp.MaxCharges; - base.AddCharges(uid, change, comp); - - // if a charge was just used from full, start the recharge timer - // TODO: probably make this an event instead of having le server system that just does this - if (change < 0 && startRecharge && TryComp(uid, out var recharge)) - recharge.NextChargeTime = _timing.CurTime + recharge.RechargeDuration; - } -} diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index 60c09efaea..f904678821 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -29,7 +29,7 @@ namespace Content.Server.Flash { [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly AudioSystem _audio = default!; - [Dependency] private readonly SharedChargesSystem _charges = default!; + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly EntityLookupSystem _entityLookup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; @@ -86,15 +86,15 @@ namespace Content.Server.Flash return false; TryComp(uid, out var charges); - if (_charges.IsEmpty(uid, charges)) + if (_sharedCharges.IsEmpty((uid, charges))) return false; - _charges.UseCharge(uid, charges); + _sharedCharges.TryUseCharge((uid, charges)); _audio.PlayPvs(comp.Sound, uid); comp.Flashing = true; _appearance.SetData(uid, FlashVisuals.Flashing, true); - if (_charges.IsEmpty(uid, charges)) + if (_sharedCharges.IsEmpty((uid, charges))) { _appearance.SetData(uid, FlashVisuals.Burnt, true); _tag.AddTag(uid, TrashTag); diff --git a/Content.Shared/Actions/BaseActionComponent.cs b/Content.Shared/Actions/BaseActionComponent.cs index 25b36df2af..05abe30f24 100644 --- a/Content.Shared/Actions/BaseActionComponent.cs +++ b/Content.Shared/Actions/BaseActionComponent.cs @@ -86,24 +86,6 @@ public abstract partial class BaseActionComponent : Component /// [DataField("useDelay")] public TimeSpan? UseDelay; - /// - /// Convenience tool for actions with limited number of charges. Automatically decremented on use, and the - /// action is disabled when it reaches zero. Does NOT automatically remove the action from the action bar. - /// However, charges will regenerate if is enabled and the action will not disable - /// when charges reach zero. - /// - [DataField("charges")] public int? Charges; - - /// - /// The max charges this action has. If null, this is set automatically from on mapinit. - /// - [DataField] public int? MaxCharges; - - /// - /// If enabled, charges will regenerate after a is complete - /// - [DataField("renewCharges")]public bool RenewCharges; - /// /// The entity that contains this action. If the action is innate, this may be the user themselves. /// This should almost always be non-null. @@ -209,9 +191,6 @@ public abstract class BaseActionComponentState : ComponentState public bool Toggled; public (TimeSpan Start, TimeSpan End)? Cooldown; public TimeSpan? UseDelay; - public int? Charges; - public int? MaxCharges; - public bool RenewCharges; public NetEntity? Container; public NetEntity? EntityIcon; public bool CheckCanInteract; @@ -243,9 +222,6 @@ public abstract class BaseActionComponentState : ComponentState Toggled = component.Toggled; Cooldown = component.Cooldown; UseDelay = component.UseDelay; - Charges = component.Charges; - MaxCharges = component.MaxCharges; - RenewCharges = component.RenewCharges; CheckCanInteract = component.CheckCanInteract; CheckConsciousness = component.CheckConsciousness; ClientExclusive = component.ClientExclusive; diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 30f1af8465..e4aa44cf54 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -21,14 +21,14 @@ namespace Content.Shared.Actions; public abstract class SharedActionsSystem : EntitySystem { [Dependency] protected readonly IGameTiming GameTiming = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedTransformSystem _transformSystem = default!; - [Dependency] private readonly ActionContainerSystem _actionContainer = default!; - [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; public override void Initialize() { @@ -69,47 +69,9 @@ public abstract class SharedActionsSystem : EntitySystem SubscribeAllEvent(OnActionRequest); } - public override void Update(float frameTime) - { - base.Update(frameTime); - - var worldActionQuery = EntityQueryEnumerator(); - while (worldActionQuery.MoveNext(out var uid, out var action)) - { - if (IsCooldownActive(action) || !ShouldResetCharges(action)) - continue; - - ResetCharges(uid, dirty: true); - } - - var instantActionQuery = EntityQueryEnumerator(); - while (instantActionQuery.MoveNext(out var uid, out var action)) - { - if (IsCooldownActive(action) || !ShouldResetCharges(action)) - continue; - - ResetCharges(uid, dirty: true); - } - - var entityActionQuery = EntityQueryEnumerator(); - while (entityActionQuery.MoveNext(out var uid, out var action)) - { - if (IsCooldownActive(action) || !ShouldResetCharges(action)) - continue; - - ResetCharges(uid, dirty: true); - } - } - private void OnActionMapInit(EntityUid uid, BaseActionComponent component, MapInitEvent args) { component.OriginalIconColor = component.IconColor; - - if (component.Charges == null) - return; - - component.MaxCharges ??= component.Charges.Value; - Dirty(uid, component); } private void OnActionShutdown(EntityUid uid, BaseActionComponent component, ComponentShutdown args) @@ -312,68 +274,6 @@ public abstract class SharedActionsSystem : EntitySystem Dirty(actionId.Value, action); } - public void SetCharges(EntityUid? actionId, int? charges) - { - if (!TryGetActionData(actionId, out var action) || - action.Charges == charges) - { - return; - } - - action.Charges = charges; - UpdateAction(actionId, action); - Dirty(actionId.Value, action); - } - - public int? GetCharges(EntityUid? actionId) - { - if (!TryGetActionData(actionId, out var action)) - return null; - - return action.Charges; - } - - public void AddCharges(EntityUid? actionId, int addCharges) - { - if (!TryGetActionData(actionId, out var action) || action.Charges == null || addCharges < 1) - return; - - action.Charges += addCharges; - UpdateAction(actionId, action); - Dirty(actionId.Value, action); - } - - public void RemoveCharges(EntityUid? actionId, int? removeCharges) - { - if (!TryGetActionData(actionId, out var action) || action.Charges == null) - return; - - if (removeCharges == null) - action.Charges = removeCharges; - else - action.Charges -= removeCharges; - - if (action.Charges is < 0) - action.Charges = null; - - UpdateAction(actionId, action); - Dirty(actionId.Value, action); - } - - public void ResetCharges(EntityUid? actionId, bool update = false, bool dirty = false) - { - if (!TryGetActionData(actionId, out var action)) - return; - - action.Charges = action.MaxCharges; - - if (update) - UpdateAction(actionId, action); - - if (dirty) - Dirty(actionId.Value, action); - } - private void OnActionsGetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args) { args.State = new ActionsComponentState(GetNetEntitySet(component.Actions)); @@ -416,6 +316,10 @@ public abstract class SharedActionsSystem : EntitySystem if (!action.Enabled) return; + var curTime = GameTiming.CurTime; + if (IsCooldownActive(action, curTime)) + return; + // check for action use prevention // TODO: make code below use this event with a dedicated component var attemptEv = new ActionAttemptEvent(user); @@ -423,14 +327,6 @@ public abstract class SharedActionsSystem : EntitySystem if (attemptEv.Cancelled) return; - var curTime = GameTiming.CurTime; - if (IsCooldownActive(action, curTime)) - return; - - // TODO: Replace with individual charge recovery when we have the visuals to aid it - if (action is { Charges: < 1, RenewCharges: true }) - ResetCharges(actionEnt, true, true); - BaseActionEvent? performEvent = null; if (action.CheckConsciousness && !_actionBlockerSystem.CanConsciouslyPerformAction(user)) @@ -705,16 +601,8 @@ public abstract class SharedActionsSystem : EntitySystem var dirty = toggledBefore != action.Toggled; - if (action.Charges != null) - { - dirty = true; - action.Charges--; - if (action is { Charges: 0, RenewCharges: false }) - action.Enabled = false; - } - action.Cooldown = null; - if (action is { UseDelay: not null, Charges: null or < 1 }) + if (action is { UseDelay: not null}) { dirty = true; action.Cooldown = (curTime, curTime + action.UseDelay.Value); @@ -1014,8 +902,6 @@ public abstract class SharedActionsSystem : EntitySystem if (!action.Enabled) return false; - if (action.Charges.HasValue && action.Charges <= 0) - return false; var curTime = GameTiming.CurTime; if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime) @@ -1125,15 +1011,9 @@ public abstract class SharedActionsSystem : EntitySystem /// /// Checks if the action has a cooldown and if it's still active /// - protected bool IsCooldownActive(BaseActionComponent action, TimeSpan? curTime = null) + public bool IsCooldownActive(BaseActionComponent action, TimeSpan? curTime = null) { - curTime ??= GameTiming.CurTime; // TODO: Check for charge recovery timer return action.Cooldown.HasValue && action.Cooldown.Value.End > curTime; } - - protected bool ShouldResetCharges(BaseActionComponent action) - { - return action is { Charges: < 1, RenewCharges: true }; - } } diff --git a/Content.Shared/Charges/Components/AutoRechargeComponent.cs b/Content.Shared/Charges/Components/AutoRechargeComponent.cs new file mode 100644 index 0000000000..704783056c --- /dev/null +++ b/Content.Shared/Charges/Components/AutoRechargeComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Charges.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Charges.Components; + +/// +/// Something with limited charges that can be recharged automatically. +/// Requires LimitedChargesComponent to function. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedChargesSystem))] +public sealed partial class AutoRechargeComponent : Component +{ + /// + /// The time it takes to regain a single charge + /// + [DataField, AutoNetworkedField] + public TimeSpan RechargeDuration = TimeSpan.FromSeconds(90); +} diff --git a/Content.Shared/Charges/Components/LimitedChargesComponent.cs b/Content.Shared/Charges/Components/LimitedChargesComponent.cs index 6973ffbe72..ff926fc158 100644 --- a/Content.Shared/Charges/Components/LimitedChargesComponent.cs +++ b/Content.Shared/Charges/Components/LimitedChargesComponent.cs @@ -1,24 +1,27 @@ using Content.Shared.Charges.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Charges.Components; -[RegisterComponent, NetworkedComponent] -[Access(typeof(SharedChargesSystem))] -[AutoGenerateComponentState] +/// +/// Specifies the attached action has discrete charges, separate to a cooldown. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedChargesSystem))] public sealed partial class LimitedChargesComponent : Component { - /// - /// The maximum number of charges - /// - [DataField("maxCharges"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public int MaxCharges = 3; + [DataField, AutoNetworkedField] + public int LastCharges; /// - /// The current number of charges + /// The max charges this action has. /// - [DataField("charges"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public int Charges = 3; + [DataField, AutoNetworkedField, Access(Other = AccessPermissions.Read)] + public int MaxCharges = 1; + + /// + /// Last time charges was changed. Used to derive current charges. + /// + [DataField(customTypeSerializer:typeof(TimeOffsetSerializer)), AutoNetworkedField] + public TimeSpan LastUpdate; } diff --git a/Content.Shared/Charges/Systems/SharedChargesSystem.cs b/Content.Shared/Charges/Systems/SharedChargesSystem.cs index 7f95ef184e..2eb05f8bfc 100644 --- a/Content.Shared/Charges/Systems/SharedChargesSystem.cs +++ b/Content.Shared/Charges/Systems/SharedChargesSystem.cs @@ -1,103 +1,232 @@ +using Content.Shared.Actions.Events; using Content.Shared.Charges.Components; using Content.Shared.Examine; +using JetBrains.Annotations; +using Robust.Shared.Timing; namespace Content.Shared.Charges.Systems; public abstract class SharedChargesSystem : EntitySystem { - protected EntityQuery Query; + [Dependency] protected readonly IGameTiming _timing = default!; + + /* + * Despite what a bunch of systems do you don't need to continuously tick linear number updates and can just derive it easily. + */ public override void Initialize() { base.Initialize(); - Query = GetEntityQuery(); - SubscribeLocalEvent(OnExamine); + + SubscribeLocalEvent(OnChargesAttempt); + SubscribeLocalEvent(OnChargesMapInit); + SubscribeLocalEvent(OnChargesPerformed); } - protected virtual void OnExamine(EntityUid uid, LimitedChargesComponent comp, ExaminedEvent args) + private void OnExamine(EntityUid uid, LimitedChargesComponent comp, ExaminedEvent args) { if (!args.IsInDetailsRange) return; - using (args.PushGroup(nameof(LimitedChargesComponent))) + var rechargeEnt = new Entity(uid, comp, null); + var charges = GetCurrentCharges(rechargeEnt); + using var _ = args.PushGroup(nameof(LimitedChargesComponent)); + + args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", charges))); + if (charges == comp.MaxCharges) { - args.PushMarkup(Loc.GetString("limited-charges-charges-remaining", ("charges", comp.Charges))); - if (comp.Charges == comp.MaxCharges) - { - args.PushMarkup(Loc.GetString("limited-charges-max-charges")); - } + args.PushMarkup(Loc.GetString("limited-charges-max-charges")); + } + + // only show the recharging info if it's not full + if (charges == comp.MaxCharges || !TryComp(uid, out var recharge)) + return; + + rechargeEnt.Comp2 = recharge; + var timeRemaining = GetNextRechargeTime(rechargeEnt); + args.PushMarkup(Loc.GetString("limited-charges-recharging", ("seconds", timeRemaining.TotalSeconds.ToString("F1")))); + } + + private void OnChargesAttempt(Entity ent, ref ActionAttemptEvent args) + { + if (args.Cancelled) + return; + + var charges = GetCurrentCharges((ent.Owner, ent.Comp, null)); + + if (charges <= 0) + { + args.Cancelled = true; } } - /// - /// Tries to add a number of charges. If it over or underflows it will be clamped, wasting the extra charges. - /// - public virtual void AddCharges(EntityUid uid, int change, LimitedChargesComponent? comp = null) + private void OnChargesPerformed(Entity ent, ref ActionPerformedEvent args) { - if (!Query.Resolve(uid, ref comp, false)) + AddCharges((ent.Owner, ent.Comp), -1); + } + + private void OnChargesMapInit(Entity ent, ref MapInitEvent args) + { + // If nothing specified use max. + if (ent.Comp.LastCharges == 0) + { + ent.Comp.LastCharges = ent.Comp.MaxCharges; + } + // If -1 used then we don't want any. + else if (ent.Comp.LastCharges < 0) + { + ent.Comp.LastCharges = 0; + } + + ent.Comp.LastUpdate = _timing.CurTime; + Dirty(ent); + } + + [Pure] + public bool HasCharges(Entity action, int charges) + { + var current = GetCurrentCharges(action); + + return current >= charges; + } + + /// + /// Adds the specified charges. Does not reset the accumulator. + /// + public void AddCharges(Entity action, int addCharges) + { + if (addCharges == 0) return; - var old = comp.Charges; - comp.Charges = Math.Clamp(comp.Charges + change, 0, comp.MaxCharges); - if (comp.Charges != old) - Dirty(uid, comp); + action.Comp ??= EnsureComp(action.Owner); + + // 1. If we're going FROM max then set lastupdate to now (so it doesn't instantly recharge). + // 2. If we're going TO max then also set lastupdate to now. + // 3. Otherwise don't modify it. + // No idea if we go to 0 but future problem. + + var lastCharges = GetCurrentCharges(action); + var charges = lastCharges + addCharges; + + if (lastCharges == charges) + return; + + if (charges == action.Comp.MaxCharges || lastCharges == action.Comp.MaxCharges) + { + action.Comp.LastUpdate = _timing.CurTime; + } + + action.Comp.LastCharges = Math.Clamp(action.Comp.LastCharges + addCharges, 0, action.Comp.MaxCharges); + Dirty(action); } - /// - /// Gets the limited charges component and returns true if there are no charges. Will return false if there is no limited charges component. - /// - public bool IsEmpty(EntityUid uid, LimitedChargesComponent? comp = null) + public bool TryUseCharge(Entity entity) { - // can't be empty if there are no limited charges - if (!Query.Resolve(uid, ref comp, false)) + return TryUseCharges(entity, 1); + } + + public bool TryUseCharges(Entity entity, int amount) + { + var current = GetCurrentCharges(entity); + + if (current < amount) + { return false; + } - return comp.Charges <= 0; - } - - /// - /// Uses a single charge. Must check IsEmpty beforehand to prevent using with 0 charge. - /// - public void UseCharge(EntityUid uid, LimitedChargesComponent? comp = null) - { - AddCharges(uid, -1, comp); - } - - /// - /// Checks IsEmpty and uses a charge if it isn't empty. - /// - public bool TryUseCharge(Entity ent) - { - if (!Query.Resolve(ent, ref ent.Comp, false)) - return true; - - if (IsEmpty(ent, ent.Comp)) - return false; - - UseCharge(ent, ent.Comp); + AddCharges(entity, -amount); return true; } - /// - /// Gets the limited charges component and returns true if the number of charges remaining is less than the specified value. - /// Will return false if there is no limited charges component. - /// - public bool HasInsufficientCharges(EntityUid uid, int requiredCharges, LimitedChargesComponent? comp = null) + [Pure] + public bool IsEmpty(Entity entity) { - // can't be empty if there are no limited charges - if (!Resolve(uid, ref comp, false)) - return false; - - return comp.Charges < requiredCharges; + return GetCurrentCharges(entity) == 0; } /// - /// Uses up a specified number of charges. Must check HasInsufficentCharges beforehand to prevent using with insufficient remaining charges. + /// Resets action charges to MaxCharges. /// - public virtual void UseCharges(EntityUid uid, int chargesUsed, LimitedChargesComponent? comp = null) + public void ResetCharges(Entity action) { - AddCharges(uid, -chargesUsed, comp); + if (!Resolve(action.Owner, ref action.Comp, false)) + return; + + var charges = GetCurrentCharges((action.Owner, action.Comp, null)); + + if (charges == action.Comp.MaxCharges) + return; + + action.Comp.LastCharges = action.Comp.MaxCharges; + action.Comp.LastUpdate = _timing.CurTime; + Dirty(action); + } + + public void SetCharges(Entity action, int value) + { + action.Comp ??= EnsureComp(action.Owner); + + var adjusted = Math.Clamp(value, 0, action.Comp.MaxCharges); + + if (action.Comp.LastCharges == adjusted) + { + return; + } + + action.Comp.LastCharges = adjusted; + action.Comp.LastUpdate = _timing.CurTime; + Dirty(action); + } + + /// + /// The next time a charge will be considered to be filled. + /// + /// 0 timespan if invalid or no charges to generate. + [Pure] + public TimeSpan GetNextRechargeTime(Entity entity) + { + if (!Resolve(entity.Owner, ref entity.Comp1, ref entity.Comp2, false)) + { + return TimeSpan.Zero; + } + + // Okay so essentially we need to get recharge time to full, then modulus that by the recharge timer which should be the next tick. + var fullTime = ((entity.Comp1.MaxCharges - entity.Comp1.LastCharges) * entity.Comp2.RechargeDuration) + entity.Comp1.LastUpdate; + var timeRemaining = fullTime - _timing.CurTime; + + if (timeRemaining < TimeSpan.Zero) + { + return TimeSpan.Zero; + } + + var nextChargeTime = timeRemaining.TotalSeconds % entity.Comp2.RechargeDuration.TotalSeconds; + return TimeSpan.FromSeconds(nextChargeTime); + } + + /// + /// Derives the current charges of an entity. + /// + [Pure] + public int GetCurrentCharges(Entity entity) + { + if (!Resolve(entity.Owner, ref entity.Comp1, false)) + { + // I'm all in favor of nullable ints however null-checking return args against comp nullability is dodgy + // so we get this. + return -1; + } + + var calculated = 0; + + if (Resolve(entity.Owner, ref entity.Comp2, false) && entity.Comp2.RechargeDuration.TotalSeconds != 0.0) + { + calculated = (int)((_timing.CurTime - entity.Comp1.LastUpdate).TotalSeconds / entity.Comp2.RechargeDuration.TotalSeconds); + } + + return Math.Clamp(entity.Comp1.LastCharges + calculated, + 0, + entity.Comp1.MaxCharges); } } diff --git a/Content.Shared/Emag/Systems/EmagSystem.cs b/Content.Shared/Emag/Systems/EmagSystem.cs index 9626f17719..7aa4303471 100644 --- a/Content.Shared/Emag/Systems/EmagSystem.cs +++ b/Content.Shared/Emag/Systems/EmagSystem.cs @@ -21,7 +21,7 @@ namespace Content.Shared.Emag.Systems; public sealed class EmagSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly SharedChargesSystem _charges = default!; + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -61,8 +61,8 @@ public sealed class EmagSystem : EntitySystem if (_tag.HasTag(target, ent.Comp.EmagImmuneTag)) return false; - TryComp(ent, out var charges); - if (_charges.IsEmpty(ent, charges)) + Entity chargesEnt = ent.Owner; + if (_sharedCharges.IsEmpty(chargesEnt)) { _popup.PopupClient(Loc.GetString("emag-no-charges"), user, user); return false; @@ -80,8 +80,8 @@ public sealed class EmagSystem : EntitySystem _adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(user):player} emagged {ToPrettyString(target):target} with flag(s): {ent.Comp.EmagType}"); - if (charges != null && emaggedEvent.Handled) - _charges.UseCharge(ent, charges); + if (emaggedEvent.Handled) + _sharedCharges.TryUseCharge(chargesEnt); if (!emaggedEvent.Repeatable) { diff --git a/Content.Shared/Magic/SpellbookSystem.cs b/Content.Shared/Magic/SpellbookSystem.cs index ce1628bacb..39fa16f622 100644 --- a/Content.Shared/Magic/SpellbookSystem.cs +++ b/Content.Shared/Magic/SpellbookSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Actions; +using Content.Shared.Charges.Systems; using Content.Shared.DoAfter; using Content.Shared.Interaction.Events; using Content.Shared.Magic.Components; @@ -9,6 +10,7 @@ namespace Content.Shared.Magic; public sealed class SpellbookSystem : EntitySystem { + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; @@ -30,11 +32,7 @@ public sealed class SpellbookSystem : EntitySystem if (spell == null) continue; - int? charge = charges; - if (_actions.GetCharges(spell) != null) - charge = _actions.GetCharges(spell); - - _actions.SetCharges(spell, charge < 0 ? null : charge); + _sharedCharges.SetCharges(spell.Value, charges); ent.Comp.Spells.Add(spell.Value); } } @@ -75,7 +73,7 @@ public sealed class SpellbookSystem : EntitySystem { EntityUid? actionId = null; if (_actions.AddAction(args.Args.User, ref actionId, id)) - _actions.SetCharges(actionId, charges < 0 ? null : charges); + _sharedCharges.SetCharges(actionId.Value, charges); } } diff --git a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs index cd8f7da768..c02d6cfd9a 100644 --- a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs +++ b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs @@ -22,7 +22,7 @@ public sealed class DashAbilitySystem : EntitySystem { [Dependency] private readonly ActionContainerSystem _actionContainer = default!; [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly SharedChargesSystem _charges = default!; + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; @@ -79,7 +79,7 @@ public sealed class DashAbilitySystem : EntitySystem return; } - if (!_charges.TryUseCharge(uid)) + if (!_sharedCharges.TryUseCharge(uid)) { _popup.PopupClient(Loc.GetString("dash-ability-no-charges", ("item", uid)), user, user); return; diff --git a/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs b/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs index 281b97a648..c2425b9264 100644 --- a/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs +++ b/Content.Shared/Ninja/Systems/EnergyKatanaSystem.cs @@ -28,6 +28,7 @@ public sealed class EnergyKatanaSystem : EntitySystem private void OnCheckDash(Entity ent, ref CheckDashEvent args) { + // Just use a whitelist fam if (!_ninja.IsNinja(args.User)) args.Cancelled = true; } diff --git a/Content.Shared/RCD/Components/RCDAmmoComponent.cs b/Content.Shared/RCD/Components/RCDAmmoComponent.cs index 4135b606e2..2c0c6bcabc 100644 --- a/Content.Shared/RCD/Components/RCDAmmoComponent.cs +++ b/Content.Shared/RCD/Components/RCDAmmoComponent.cs @@ -11,6 +11,6 @@ public sealed partial class RCDAmmoComponent : Component /// How many charges are contained in this ammo cartridge. /// Can be partially transferred into an RCD, until it is empty then it gets deleted. /// - [DataField("charges"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [DataField, AutoNetworkedField] public int Charges = 30; } diff --git a/Content.Shared/RCD/Systems/RCDAmmoSystem.cs b/Content.Shared/RCD/Systems/RCDAmmoSystem.cs index 9cb3c26485..eb770f2898 100644 --- a/Content.Shared/RCD/Systems/RCDAmmoSystem.cs +++ b/Content.Shared/RCD/Systems/RCDAmmoSystem.cs @@ -10,7 +10,7 @@ namespace Content.Shared.RCD.Systems; public sealed class RCDAmmoSystem : EntitySystem { - [Dependency] private readonly SharedChargesSystem _charges = default!; + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly IGameTiming _timing = default!; @@ -41,9 +41,10 @@ public sealed class RCDAmmoSystem : EntitySystem !TryComp(target, out var charges)) return; + var current = _sharedCharges.GetCurrentCharges((target, charges)); var user = args.User; args.Handled = true; - var count = Math.Min(charges.MaxCharges - charges.Charges, comp.Charges); + var count = Math.Min(charges.MaxCharges - current, comp.Charges); if (count <= 0) { _popup.PopupClient(Loc.GetString("rcd-ammo-component-after-interact-full"), target, user); @@ -51,7 +52,7 @@ public sealed class RCDAmmoSystem : EntitySystem } _popup.PopupClient(Loc.GetString("rcd-ammo-component-after-interact-refilled"), target, user); - _charges.AddCharges(target, count, charges); + _sharedCharges.AddCharges(target, count); comp.Charges -= count; Dirty(uid, comp); diff --git a/Content.Shared/RCD/Systems/RCDSystem.cs b/Content.Shared/RCD/Systems/RCDSystem.cs index 83d6660e8e..2025f2f3cc 100644 --- a/Content.Shared/RCD/Systems/RCDSystem.cs +++ b/Content.Shared/RCD/Systems/RCDSystem.cs @@ -33,7 +33,7 @@ public sealed class RCDSystem : EntitySystem [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] private readonly FloorTileSystem _floors = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedChargesSystem _charges = default!; + [Dependency] private readonly SharedChargesSystem _sharedCharges = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; @@ -43,7 +43,6 @@ public sealed class RCDSystem : EntitySystem [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly TagSystem _tags = default!; - [Dependency] private readonly SharedTransformSystem _transformSystem = default!; private readonly int _instantConstructionDelay = 0; private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0"; @@ -133,7 +132,7 @@ public sealed class RCDSystem : EntitySystem if (!location.IsValid(EntityManager)) return; - var gridUid = _transformSystem.GetGrid(location); + var gridUid = _transform.GetGrid(location); if (!TryComp(gridUid, out var mapGrid)) { @@ -239,7 +238,7 @@ public sealed class RCDSystem : EntitySystem // Ensure the RCD operation is still valid var location = GetCoordinates(args.Event.Location); - var gridUid = _transformSystem.GetGrid(location); + var gridUid = _transform.GetGrid(location); if (!TryComp(gridUid, out var mapGrid)) { @@ -272,7 +271,7 @@ public sealed class RCDSystem : EntitySystem var location = GetCoordinates(args.Location); - var gridUid = _transformSystem.GetGrid(location); + var gridUid = _transform.GetGrid(location); if (!TryComp(gridUid, out var mapGrid)) return; @@ -289,7 +288,7 @@ public sealed class RCDSystem : EntitySystem // Play audio and consume charges _audio.PlayPredicted(component.SuccessSound, uid, args.User); - _charges.UseCharges(uid, args.Cost); + _sharedCharges.AddCharges(uid, -args.Cost); } private void OnRCDconstructionGhostRotationEvent(RCDConstructionGhostRotationEvent ev, EntitySessionEventArgs session) @@ -321,10 +320,10 @@ public sealed class RCDSystem : EntitySystem var prototype = _protoManager.Index(component.ProtoId); // Check that the RCD has enough ammo to get the job done - TryComp(uid, out var charges); + var charges = _sharedCharges.GetCurrentCharges(uid); // Both of these were messages were suppose to be predicted, but HasInsufficientCharges wasn't being checked on the client for some reason? - if (_charges.IsEmpty(uid, charges)) + if (charges == 0) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-no-ammo-message"), uid, user); @@ -332,7 +331,7 @@ public sealed class RCDSystem : EntitySystem return false; } - if (_charges.HasInsufficientCharges(uid, prototype.Cost, charges)) + if (prototype.Cost > charges) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-insufficient-ammo-message"), uid, user); diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index b1c68f9d0e..e1da9bcf1f 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -93,8 +93,9 @@ name: Break Free description: Activating your freedom implant will free you from any hand restraints components: + - type: LimitedCharges + maxCharges: 3 - type: InstantAction - charges: 3 checkCanInteract: false itemIconStyle: BigAction priority: -20 @@ -121,9 +122,10 @@ name: Activate EMP description: Triggers a small EMP pulse around you components: + - type: LimitedCharges + maxCharges: 3 - type: InstantAction checkCanInteract: false - charges: 3 useDelay: 5 itemIconStyle: BigAction priority: -20 @@ -137,9 +139,10 @@ name: SCRAM! description: Randomly teleports you within a large distance. components: + - type: LimitedCharges + maxCharges: 2 - type: InstantAction checkCanInteract: false - charges: 2 useDelay: 5 itemIconStyle: BigAction priority: -20 @@ -155,8 +158,9 @@ components: - type: ConfirmableAction popup: dna-scrambler-action-popup + - type: LimitedCharges + maxCharges: 1 - type: InstantAction - charges: 1 itemIconStyle: BigAction priority: -20 icon: diff --git a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml index 9b27ad0bc6..a6ab09699f 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml @@ -159,7 +159,6 @@ - type: Flash - type: LimitedCharges maxCharges: 3 - charges: 3 - type: AutoRecharge rechargeDuration: 30 - type: MeleeWeapon diff --git a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml index 89101e34ff..f5ea2e6f27 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml @@ -88,7 +88,6 @@ - type: Flash - type: LimitedCharges maxCharges: 15 - charges: 15 - type: MeleeWeapon damage: types: diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 11f40764b7..d84dec120a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -320,7 +320,6 @@ - Deconstruct - type: LimitedCharges maxCharges: 30 - charges: 30 - type: Sprite sprite: Objects/Tools/rcd.rsi state: icon @@ -351,7 +350,7 @@ suffix: Empty components: - type: LimitedCharges - charges: 0 + lastCharges: -1 - type: entity id: RCDRecharging @@ -362,7 +361,6 @@ components: - type: LimitedCharges maxCharges: 20 - charges: 20 - type: AutoRecharge rechargeDuration: 10 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index 44e98538ad..a87f380646 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -89,7 +89,6 @@ - type: DashAbility - type: LimitedCharges maxCharges: 3 - charges: 3 - type: AutoRecharge rechargeDuration: 20 - type: Clothing diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 59889ce649..ff54af9518 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -147,7 +147,6 @@ - type: Flash - type: LimitedCharges maxCharges: 5 - charges: 5 - type: MeleeWeapon wideAnimationRotation: 180 damage: @@ -183,7 +182,6 @@ components: - type: LimitedCharges maxCharges: 2 - charges: 2 - type: entity name: portable flasher diff --git a/Resources/Prototypes/Magic/animate_spell.yml b/Resources/Prototypes/Magic/animate_spell.yml index a9609c4f8a..d36afb49d9 100644 --- a/Resources/Prototypes/Magic/animate_spell.yml +++ b/Resources/Prototypes/Magic/animate_spell.yml @@ -3,9 +3,10 @@ name: Animate description: Bring an inanimate object to life! components: + - type: LimitedCharges + maxCharges: 5 - type: EntityTargetAction useDelay: 0 - charges: 5 itemIconStyle: BigAction whitelist: components: diff --git a/Resources/Prototypes/Magic/projectile_spells.yml b/Resources/Prototypes/Magic/projectile_spells.yml index 71bbc096c5..eee8b1fc8a 100644 --- a/Resources/Prototypes/Magic/projectile_spells.yml +++ b/Resources/Prototypes/Magic/projectile_spells.yml @@ -30,8 +30,6 @@ description: Fires a fireball, but faster! components: - type: WorldTargetAction - useDelay: 10 - renewCharges: true itemIconStyle: BigAction checkCanAccess: false raiseOnUser: true @@ -52,8 +50,6 @@ description: The fastest fireball in the west! components: - type: WorldTargetAction - useDelay: 8 - renewCharges: true itemIconStyle: BigAction checkCanAccess: false raiseOnUser: true diff --git a/Resources/Prototypes/Magic/staves.yml b/Resources/Prototypes/Magic/staves.yml index 0582899495..8bfb30b887 100644 --- a/Resources/Prototypes/Magic/staves.yml +++ b/Resources/Prototypes/Magic/staves.yml @@ -59,9 +59,10 @@ - type: entity id: ActionRgbLight components: + - type: LimitedCharges + maxCharges: 25 - type: EntityTargetAction whitelist: { components: [ PointLight ] } - charges: 25 sound: /Audio/Magic/blink.ogg event: !type:ChangeComponentsSpellEvent toAdd: