diff --git a/Content.Client/Entry/IgnoredComponents.cs b/Content.Client/Entry/IgnoredComponents.cs index cc85722423..1b2b63cc5e 100644 --- a/Content.Client/Entry/IgnoredComponents.cs +++ b/Content.Client/Entry/IgnoredComponents.cs @@ -10,6 +10,7 @@ namespace Content.Client.Entry "Pickaxe", "IngestionBlocker", "Interactable", + "Charger", "CloningPod", "Destructible", "Temperature", @@ -35,11 +36,6 @@ namespace Content.Client.Entry "Material", "RangedMagazine", "Ammo", - "HitscanWeaponCapacitor", - "PowerCell", - "PowerCellCharger", - "PowerCellSlot", - "WeaponCapacitorCharger", "AiController", "Computer", "AsteroidRock", diff --git a/Content.Client/Light/Components/HandheldLightComponent.cs b/Content.Client/Light/Components/HandheldLightComponent.cs index f357d17ae1..eac211e864 100644 --- a/Content.Client/Light/Components/HandheldLightComponent.cs +++ b/Content.Client/Light/Components/HandheldLightComponent.cs @@ -16,8 +16,6 @@ namespace Content.Client.Light.Components [Friend(typeof(HandheldLightSystem))] public sealed class HandheldLightComponent : SharedHandheldLightComponent, IItemStatus { - [ViewVariables] protected override bool HasCell => Level != null; - public byte? Level; public Control MakeControl() @@ -69,9 +67,6 @@ namespace Content.Client.Light.Components { base.FrameUpdate(args); - if (!_parent.HasCell) - return; - _timer += args.DeltaSeconds; _timer %= TimerCycle; @@ -81,7 +76,7 @@ namespace Content.Client.Light.Components { if (i == 0) { - if (level == 0) + if (level == 0 || level == null) { _sections[0].PanelOverride = StyleBoxUnlit; } diff --git a/Content.Client/PowerCell/PowerCellSystem.cs b/Content.Client/PowerCell/PowerCellSystem.cs new file mode 100644 index 0000000000..9d9ea40303 --- /dev/null +++ b/Content.Client/PowerCell/PowerCellSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.PowerCell; +using JetBrains.Annotations; + +namespace Content.Client.PowerCell; + +[UsedImplicitly] +public sealed class PowerCellSystem : SharedPowerCellSystem { } diff --git a/Content.Client/Weapons/Ranged/Barrels/Components/ClientBatteryBarrelComponent.cs b/Content.Client/Weapons/Ranged/Barrels/Components/ClientBatteryBarrelComponent.cs index 4ef5813498..37583c46a2 100644 --- a/Content.Client/Weapons/Ranged/Barrels/Components/ClientBatteryBarrelComponent.cs +++ b/Content.Client/Weapons/Ranged/Barrels/Components/ClientBatteryBarrelComponent.cs @@ -23,9 +23,6 @@ namespace Content.Client.Weapons.Ranged.Barrels.Components private StatusControl? _statusControl; - [DataField("cellSlot", required: true)] - public ItemSlot CellSlot = default!; - /// /// Count of bullets in the magazine. /// @@ -35,18 +32,6 @@ namespace Content.Client.Weapons.Ranged.Barrels.Components [ViewVariables] public (int count, int max)? MagazineCount { get; private set; } - protected override void Initialize() - { - base.Initialize(); - EntitySystem.Get().AddItemSlot(Owner, $"{Name}-powercell-container", CellSlot); - } - - protected override void OnRemove() - { - base.OnRemove(); - EntitySystem.Get().RemoveItemSlot(Owner, CellSlot); - } - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) { base.HandleComponentState(curState, nextState); diff --git a/Content.Server/Light/Components/HandheldLightComponent.cs b/Content.Server/Light/Components/HandheldLightComponent.cs index 95fd615820..7f84497f66 100644 --- a/Content.Server/Light/Components/HandheldLightComponent.cs +++ b/Content.Server/Light/Components/HandheldLightComponent.cs @@ -1,9 +1,6 @@ using System.Threading.Tasks; using Content.Server.Clothing.Components; using Content.Server.Light.EntitySystems; -using Content.Server.PowerCell.Components; -using Content.Shared.ActionBlocker; -using Content.Shared.Actions; using Content.Shared.Actions.Behaviors.Item; using Content.Shared.Examine; using Content.Shared.Interaction; @@ -34,8 +31,6 @@ namespace Content.Server.Light.Components public sealed class HandheldLightComponent : SharedHandheldLightComponent { [ViewVariables(VVAccess.ReadWrite)] [DataField("wattage")] public float Wattage { get; set; } = 3f; - [ViewVariables] public PowerCellSlotComponent CellSlot = default!; - public PowerCellComponent? Cell => CellSlot.Cell; /// /// Status of light, whether or not it is emitting light. @@ -43,8 +38,6 @@ namespace Content.Server.Light.Components [ViewVariables] public bool Activated { get; set; } - [ViewVariables] protected override bool HasCell => CellSlot.HasCell; - [ViewVariables(VVAccess.ReadWrite)] [DataField("turnOnSound")] public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Items/flashlight_on.ogg"); [ViewVariables(VVAccess.ReadWrite)] [DataField("turnOnFailSound")] public SoundSpecifier TurnOnFailSound = new SoundPathSpecifier("/Audio/Machines/button.ogg"); [ViewVariables(VVAccess.ReadWrite)] [DataField("turnOffSound")] public SoundSpecifier TurnOffSound = new SoundPathSpecifier("/Audio/Items/flashlight_off.ogg"); diff --git a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs index 96ff42e711..4322298762 100644 --- a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs +++ b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Content.Server.Clothing.Components; using Content.Server.Light.Components; using Content.Server.Popups; -using Content.Server.PowerCell.Components; +using Content.Server.PowerCell; using Content.Shared.ActionBlocker; using Content.Shared.Actions; using Content.Shared.Actions.Components; @@ -29,6 +29,7 @@ namespace Content.Server.Light.EntitySystems { [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; // TODO: Ideally you'd be able to subscribe to power stuff to get events at certain percentages.. or something? // But for now this will be better anyway. @@ -44,7 +45,7 @@ namespace Content.Server.Light.EntitySystems SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(AddToggleLightVerb); - SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnActivate); } @@ -57,21 +58,19 @@ namespace Content.Server.Light.EntitySystems { // Curently every single flashlight has the same number of levels for status and that's all it uses the charge for // Thus we'll just check if the level changes. - if (component.Cell == null) + + if (!_powerCell.TryGetBatteryFromSlot(component.Owner, out var battery)) return null; - var currentCharge = component.Cell.CurrentCharge; - - if (MathHelper.CloseToPercent(currentCharge, 0) || component.Wattage > currentCharge) + if (MathHelper.CloseToPercent(battery.CurrentCharge, 0) || component.Wattage > battery.CurrentCharge) return 0; - return (byte?) ContentHelpers.RoundToNearestLevels(currentCharge / component.Cell.MaxCharge * 255, 255, SharedHandheldLightComponent.StatusLevels); + return (byte?) ContentHelpers.RoundToNearestLevels(battery.CurrentCharge / battery.MaxCharge * 255, 255, SharedHandheldLightComponent.StatusLevels); } private void OnInit(EntityUid uid, HandheldLightComponent component, ComponentInit args) { EntityManager.EnsureComponent(uid); - component.CellSlot = EntityManager.EnsureComponent(uid); // Want to make sure client has latest data on level so battery displays properly. component.Dirty(EntityManager); @@ -82,17 +81,6 @@ namespace Content.Server.Light.EntitySystems _activeLights.Remove(component); } - private void OnInteractUsing(EntityUid uid, HandheldLightComponent component, InteractUsingEvent args) - { - // TODO: https://github.com/space-wizards/space-station-14/pull/5864#discussion_r775191916 - if (args.Handled) return; - - if (!_blocker.CanInteract(args.User)) return; - if (!component.CellSlot.InsertCell(args.Used)) return; - component.Dirty(EntityManager); - args.Handled = true; - } - private void OnActivate(EntityUid uid, HandheldLightComponent component, ActivateInWorldEvent args) { if (args.Handled) return; @@ -183,7 +171,7 @@ namespace Content.Server.Light.EntitySystems { if (component.Activated) return false; - if (component.Cell == null) + if (!_powerCell.TryGetBatteryFromSlot(component.Owner, out var battery)) { SoundSystem.Play(Filter.Pvs(component.Owner), component.TurnOnFailSound.GetSound(), component.Owner); _popup.PopupEntity(Loc.GetString("handheld-light-component-cell-missing-message"), component.Owner, Filter.Entities(user)); @@ -194,7 +182,7 @@ namespace Content.Server.Light.EntitySystems // To prevent having to worry about frame time in here. // Let's just say you need a whole second of charge before you can turn it on. // Simple enough. - if (component.Wattage > component.Cell.CurrentCharge) + if (component.Wattage > battery.CurrentCharge) { SoundSystem.Play(Filter.Pvs(component.Owner), component.TurnOnFailSound.GetSound(), component.Owner); _popup.PopupEntity(Loc.GetString("handheld-light-component-cell-dead-message"), component.Owner, Filter.Entities(user)); @@ -241,7 +229,7 @@ namespace Content.Server.Light.EntitySystems public void TryUpdate(HandheldLightComponent component, float frameTime) { - if (component.Cell == null) + if (!_powerCell.TryGetBatteryFromSlot(component.Owner, out var battery)) { TurnOff(component, false); return; @@ -249,11 +237,12 @@ namespace Content.Server.Light.EntitySystems var appearanceComponent = EntityManager.GetComponent(component.Owner); - if (component.Cell.MaxCharge - component.Cell.CurrentCharge < component.Cell.MaxCharge * 0.70) + var fraction = battery.CurrentCharge / battery.MaxCharge; + if (fraction >= 0.30) { appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower); } - else if (component.Cell.MaxCharge - component.Cell.CurrentCharge < component.Cell.MaxCharge * 0.90) + else if (fraction >= 0.10) { appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower); } @@ -262,7 +251,8 @@ namespace Content.Server.Light.EntitySystems appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.Dying); } - if (component.Activated && !component.Cell.TryUseCharge(component.Wattage * frameTime)) TurnOff(component, false); + if (component.Activated && !battery.TryUseCharge(component.Wattage * frameTime)) + TurnOff(component, false); var level = GetLevel(component); diff --git a/Content.Server/Power/Components/BaseCharger.cs b/Content.Server/Power/Components/BaseCharger.cs deleted file mode 100644 index 6066077652..0000000000 --- a/Content.Server/Power/Components/BaseCharger.cs +++ /dev/null @@ -1,230 +0,0 @@ -using System; -using System.Threading.Tasks; -using Content.Server.Hands.Components; -using Content.Server.Weapon.Ranged.Barrels.Components; -using Content.Shared.Interaction; -using Content.Shared.Item; -using Content.Shared.Popups; -using Content.Shared.Power; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Power.Components -{ - [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(IInteractUsing))] - public abstract class BaseCharger : Component, IActivate, IInteractUsing - { - [Dependency] private readonly IEntityManager _entMan = default!; - - [ViewVariables] - private BatteryComponent? _heldBattery; - - [ViewVariables] - public ContainerSlot Container = default!; - - public bool HasCell => Container.ContainedEntity != null; - - [ViewVariables] - private CellChargerStatus _status; - - [ViewVariables] - [DataField("chargeRate")] - private int _chargeRate = 100; - - [ViewVariables] - [DataField("transferEfficiency")] - private float _transferEfficiency = 0.85f; - - protected override void Initialize() - { - base.Initialize(); - - Owner.EnsureComponent(); - Container = ContainerHelpers.EnsureContainer(Owner, $"{Name}-powerCellContainer"); - // Default state in the visualizer is OFF, so when this gets powered on during initialization it will generally show empty - } - - [Obsolete("Component Messages are deprecated, use Entity Events instead.")] - public override void HandleMessage(ComponentMessage message, IComponent? component) - { -#pragma warning disable 618 - base.HandleMessage(message, component); -#pragma warning restore 618 - switch (message) - { - case PowerChangedMessage powerChanged: - PowerUpdate(powerChanged); - break; - } - } - - protected override void OnRemove() - { - _heldBattery = null; - - base.OnRemove(); - } - - async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) - { - var result = TryInsertItem(eventArgs.Using); - if (!result) - { - eventArgs.User.PopupMessage(Owner, Loc.GetString("base-charger-on-interact-using-fail")); - } - - return result; - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - RemoveItem(eventArgs.User); - } - - /// - /// This will remove the item directly into the user's hand / floor - /// - /// - public void RemoveItem(EntityUid user) - { - if (Container.ContainedEntity is not {Valid: true} heldItem) - { - return; - } - - Container.Remove(heldItem); - _heldBattery = null; - if (_entMan.TryGetComponent(user, out HandsComponent? handsComponent)) - { - handsComponent.PutInHandOrDrop(_entMan.GetComponent(heldItem)); - } - - if (_entMan.TryGetComponent(heldItem, out ServerBatteryBarrelComponent? batteryBarrelComponent)) - { - batteryBarrelComponent.UpdateAppearance(); - } - - UpdateStatus(); - } - - private void PowerUpdate(PowerChangedMessage eventArgs) - { - UpdateStatus(); - } - - private CellChargerStatus GetStatus() - { - if (_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) && - !receiver.Powered) - { - return CellChargerStatus.Off; - } - if (!HasCell) - { - return CellChargerStatus.Empty; - } - if (_heldBattery != null && Math.Abs(_heldBattery.MaxCharge - _heldBattery.CurrentCharge) < 0.01) - { - return CellChargerStatus.Charged; - } - return CellChargerStatus.Charging; - } - - public bool TryInsertItem(EntityUid entity) - { - if (!IsEntityCompatible(entity) || HasCell) - { - return false; - } - if (!Container.Insert(entity)) - { - return false; - } - _heldBattery = GetBatteryFrom(entity); - UpdateStatus(); - return true; - } - - /// - /// If the supplied entity should fit into the charger. - /// - public abstract bool IsEntityCompatible(EntityUid entity); - - protected abstract BatteryComponent? GetBatteryFrom(EntityUid entity); - - private void UpdateStatus() - { - // Not called UpdateAppearance just because it messes with the load - var status = GetStatus(); - if (_status == status || - !_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver)) - { - return; - } - - _status = status; - _entMan.TryGetComponent(Owner, out AppearanceComponent? appearance); - - switch (_status) - { - // Update load just in case - case CellChargerStatus.Off: - receiver.Load = 0; - appearance?.SetData(CellVisual.Light, CellChargerStatus.Off); - break; - case CellChargerStatus.Empty: - receiver.Load = 0; - appearance?.SetData(CellVisual.Light, CellChargerStatus.Empty); - break; - case CellChargerStatus.Charging: - receiver.Load = (int) (_chargeRate / _transferEfficiency); - appearance?.SetData(CellVisual.Light, CellChargerStatus.Charging); - break; - case CellChargerStatus.Charged: - receiver.Load = 0; - appearance?.SetData(CellVisual.Light, CellChargerStatus.Charged); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - appearance?.SetData(CellVisual.Occupied, HasCell); - } - - public void OnUpdate(float frameTime) //todo: make single system for this - { - if (_status == CellChargerStatus.Empty || _status == CellChargerStatus.Charged || !HasCell) - { - return; - } - TransferPower(frameTime); - } - - private void TransferPower(float frameTime) - { - if (_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) && - !receiver.Powered) - { - return; - } - - if (_heldBattery == null) - { - return; - } - - _heldBattery.CurrentCharge += _chargeRate * frameTime; - // Just so the sprite won't be set to 99.99999% visibility - if (_heldBattery.MaxCharge - _heldBattery.CurrentCharge < 0.01) - { - _heldBattery.CurrentCharge = _heldBattery.MaxCharge; - } - UpdateStatus(); - } - } -} diff --git a/Content.Server/Power/Components/BatteryComponent.cs b/Content.Server/Power/Components/BatteryComponent.cs index af62a041e5..641bfcca30 100644 --- a/Content.Server/Power/Components/BatteryComponent.cs +++ b/Content.Server/Power/Components/BatteryComponent.cs @@ -1,5 +1,6 @@ using System; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -12,6 +13,8 @@ namespace Content.Server.Power.Components [RegisterComponent] public class BatteryComponent : Component { + [Dependency] private readonly IEntityManager _entMan = default!; + public override string Name => "Battery"; /// @@ -71,7 +74,10 @@ namespace Content.Server.Power.Components } } - protected virtual void OnChargeChanged() { } + protected virtual void OnChargeChanged() + { + _entMan.EventBus.RaiseLocalEvent(Owner, new ChargeChangedEvent(), false); + } private void SetMaxCharge(float newMax) { @@ -86,4 +92,6 @@ namespace Content.Server.Power.Components OnChargeChanged(); } } + + public class ChargeChangedEvent : EventArgs { } } diff --git a/Content.Server/Power/Components/ChargerComponent.cs b/Content.Server/Power/Components/ChargerComponent.cs new file mode 100644 index 0000000000..f4fec8f6e4 --- /dev/null +++ b/Content.Server/Power/Components/ChargerComponent.cs @@ -0,0 +1,121 @@ +using System; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Power; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Power.Components +{ + [RegisterComponent] + public sealed class ChargerComponent : Component + { + [Dependency] private readonly IEntityManager _entMan = default!; + + public override string Name => "Charger"; + + [ViewVariables] + public BatteryComponent? HeldBattery; + + [ViewVariables] + private CellChargerStatus _status; + + [DataField("chargeRate")] + private int _chargeRate = 100; + + [DataField("transferEfficiency")] + private float _transferEfficiency = 0.85f; + + [DataField("chargerSlot", required: true)] + public ItemSlot ChargerSlot = new(); + + private CellChargerStatus GetStatus() + { + if (_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) && + !receiver.Powered) + { + return CellChargerStatus.Off; + } + if (!ChargerSlot.HasItem) + { + return CellChargerStatus.Empty; + } + if (HeldBattery != null && Math.Abs(HeldBattery.MaxCharge - HeldBattery.CurrentCharge) < 0.01) + { + return CellChargerStatus.Charged; + } + return CellChargerStatus.Charging; + } + + public void UpdateStatus() + { + // Not called UpdateAppearance just because it messes with the load + var status = GetStatus(); + if (_status == status || + !_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver)) + { + return; + } + + _status = status; + _entMan.TryGetComponent(Owner, out AppearanceComponent? appearance); + + switch (_status) + { + // Update load just in case + case CellChargerStatus.Off: + receiver.Load = 0; + appearance?.SetData(CellVisual.Light, CellChargerStatus.Off); + break; + case CellChargerStatus.Empty: + receiver.Load = 0; + appearance?.SetData(CellVisual.Light, CellChargerStatus.Empty); + break; + case CellChargerStatus.Charging: + receiver.Load = (int) (_chargeRate / _transferEfficiency); + appearance?.SetData(CellVisual.Light, CellChargerStatus.Charging); + break; + case CellChargerStatus.Charged: + receiver.Load = 0; + appearance?.SetData(CellVisual.Light, CellChargerStatus.Charged); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + appearance?.SetData(CellVisual.Occupied, ChargerSlot.HasItem); + } + + public void OnUpdate(float frameTime) //todo: make single system for this + { + if (_status == CellChargerStatus.Empty || _status == CellChargerStatus.Charged || !ChargerSlot.HasItem) + { + return; + } + TransferPower(frameTime); + } + + private void TransferPower(float frameTime) + { + if (_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) && + !receiver.Powered) + { + return; + } + + if (HeldBattery == null) + { + return; + } + + HeldBattery.CurrentCharge += _chargeRate * frameTime; + // Just so the sprite won't be set to 99.99999% visibility + if (HeldBattery.MaxCharge - HeldBattery.CurrentCharge < 0.01) + { + HeldBattery.CurrentCharge = HeldBattery.MaxCharge; + } + UpdateStatus(); + } + } +} diff --git a/Content.Server/Power/EntitySystems/BaseChargerSystem.cs b/Content.Server/Power/EntitySystems/BaseChargerSystem.cs deleted file mode 100644 index a7aafce0e4..0000000000 --- a/Content.Server/Power/EntitySystems/BaseChargerSystem.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Content.Server.Power.Components; -using Content.Server.PowerCell.Components; -using Content.Server.Weapon; -using Content.Shared.ActionBlocker; -using Content.Shared.Verbs; -using JetBrains.Annotations; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.Power.EntitySystems -{ - [UsedImplicitly] - internal sealed class BaseChargerSystem : EntitySystem - { - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(AddEjectVerb); - SubscribeLocalEvent(AddInsertVerb); - SubscribeLocalEvent(AddEjectVerb); - SubscribeLocalEvent(AddInsertVerb); - } - - public override void Update(float frameTime) - { - foreach (var comp in EntityManager.EntityQuery()) - { - comp.OnUpdate(frameTime); - } - } - - // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? - private void AddEjectVerb(EntityUid uid, BaseCharger component, GetAlternativeVerbsEvent args) - { - if (args.Hands == null || - !args.CanAccess || - !args.CanInteract || - !component.HasCell || - !_actionBlockerSystem.CanPickup(args.User)) - return; - - Verb verb = new(); - verb.Text = EntityManager.GetComponent(component.Container.ContainedEntity!.Value).EntityName; - verb.Category = VerbCategory.Eject; - verb.Act = () => component.RemoveItem(args.User); - args.Verbs.Add(verb); - } - - private void AddInsertVerb(EntityUid uid, BaseCharger component, GetInteractionVerbsEvent args) - { - if (args.Using is not {Valid: true} @using || - !args.CanAccess || - !args.CanInteract || - component.HasCell || - !component.IsEntityCompatible(@using) || - !_actionBlockerSystem.CanDrop(args.User)) - return; - - Verb verb = new(); - verb.Text = EntityManager.GetComponent(@using).EntityName; - verb.Category = VerbCategory.Insert; - verb.Act = () => component.TryInsertItem(@using); - args.Verbs.Add(verb); - } - } -} diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs new file mode 100644 index 0000000000..ef5669948d --- /dev/null +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -0,0 +1,97 @@ +using Content.Server.Power.Components; +using Content.Server.PowerCell; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.PowerCell.Components; +using JetBrains.Annotations; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.Power.EntitySystems; + +[UsedImplicitly] +internal sealed class ChargerSystem : EntitySystem +{ + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + [Dependency] private readonly PowerCellSystem _cellSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnChargerInit); + SubscribeLocalEvent(OnChargerRemove); + + SubscribeLocalEvent(OnPowerChanged); + + SubscribeLocalEvent(OnInserted); + SubscribeLocalEvent(OnRemoved); + SubscribeLocalEvent(OnInsertAttempt); + } + + public override void Update(float frameTime) + { + foreach (var comp in EntityManager.EntityQuery()) + { + comp.OnUpdate(frameTime); + } + } + private void OnChargerInit(EntityUid uid, ChargerComponent component, ComponentInit args) + { + _itemSlotsSystem.AddItemSlot(uid, "charger-slot", component.ChargerSlot); + } + + private void OnChargerRemove(EntityUid uid, ChargerComponent component, ComponentRemove args) + { + _itemSlotsSystem.RemoveItemSlot(uid, component.ChargerSlot); + } + + private void OnPowerChanged(EntityUid uid, ChargerComponent component, PowerChangedEvent args) + { + component.UpdateStatus(); + } + + private void OnInserted(EntityUid uid, ChargerComponent component, EntInsertedIntoContainerMessage args) + { + if (!component.Initialized) + return; + + if (args.Container.ID != component.ChargerSlot.ID) + return; + + // try get a battery directly on the inserted entity + if (!TryComp(args.Entity, out component.HeldBattery)) + { + // or by checking for a power cell slot on the inserted entity + _cellSystem.TryGetBatteryFromSlot(args.Entity, out component.HeldBattery); + } + + component.UpdateStatus(); + } + + private void OnRemoved(EntityUid uid, ChargerComponent component, EntRemovedFromContainerMessage args) + { + if (args.Container.ID != component.ChargerSlot.ID) + return; + + component.UpdateStatus(); + } + + /// + /// Verify that the entity being inserted is actually rechargeable. + /// + private void OnInsertAttempt(EntityUid uid, ChargerComponent component, ContainerIsInsertingAttemptEvent args) + { + if (!component.Initialized) + return; + + if (args.Container.ID != component.ChargerSlot.ID) + return; + + if (!TryComp(args.EntityUid, out PowerCellSlotComponent? cellSlot)) + return; + + if (!cellSlot.FitsInCharger || !cellSlot.CellSlot.HasItem) + args.Cancel(); + } +} diff --git a/Content.Server/PowerCell/Components/PowerCellChargerComponent.cs b/Content.Server/PowerCell/Components/PowerCellChargerComponent.cs deleted file mode 100644 index 69b52d42c7..0000000000 --- a/Content.Server/PowerCell/Components/PowerCellChargerComponent.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Server.Power.Components; -using Content.Shared.Interaction; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.PowerCell.Components -{ - /// - /// Recharges an entity with a . - /// - [RegisterComponent] - [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(BaseCharger))] - public sealed class PowerCellChargerComponent : BaseCharger - { - public override string Name => "PowerCellCharger"; - - public override bool IsEntityCompatible(EntityUid entity) - { - return IoCManager.Resolve().HasComponent(entity); - } - - protected override BatteryComponent GetBatteryFrom(EntityUid entity) - { - return IoCManager.Resolve().GetComponent(entity); - } - } -} diff --git a/Content.Server/PowerCell/Components/PowerCellComponent.cs b/Content.Server/PowerCell/Components/PowerCellComponent.cs deleted file mode 100644 index c0cda8d0ff..0000000000 --- a/Content.Server/PowerCell/Components/PowerCellComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using Content.Server.Explosion.EntitySystems; -using Content.Server.Power.Components; -using Content.Shared.Examine; -using Content.Shared.PowerCell; -using Content.Shared.Rounding; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; - -namespace Content.Server.PowerCell.Components -{ - /// - /// Batteries that can update an based on their charge percent - /// and fit into a of the appropriate size. - /// - [RegisterComponent] - [ComponentReference(typeof(BatteryComponent))] -#pragma warning disable 618 - public class PowerCellComponent : BatteryComponent, IExamine -#pragma warning restore 618 - { - public override string Name => "PowerCell"; - public const string SolutionName = "powerCell"; - - [ViewVariables] public PowerCellSize CellSize => _cellSize; - [DataField("cellSize")] - private PowerCellSize _cellSize = PowerCellSize.Small; - - [ViewVariables] public bool IsRigged { get; set; } - - protected override void Initialize() - { - base.Initialize(); - CurrentCharge = MaxCharge; - UpdateVisuals(); - } - - protected override void OnChargeChanged() - { - base.OnChargeChanged(); - UpdateVisuals(); - } - - public override bool TryUseCharge(float chargeToUse) - { - if (IsRigged) - { - Explode(); - return false; - } - - return base.TryUseCharge(chargeToUse); - } - - public override float UseCharge(float toDeduct) - { - if (IsRigged) - { - Explode(); - return 0; - } - - return base.UseCharge(toDeduct); - } - - private void Explode() - { - var heavy = (int) Math.Ceiling(Math.Sqrt(CurrentCharge) / 60); - var light = (int) Math.Ceiling(Math.Sqrt(CurrentCharge) / 30); - - CurrentCharge = 0; - EntitySystem.Get().SpawnExplosion(Owner, 0, heavy, light, light*2); - IoCManager.Resolve().DeleteEntity(Owner); - } - - private void UpdateVisuals() - { - if (IoCManager.Resolve().TryGetComponent(Owner, out AppearanceComponent? appearance)) - { - appearance.SetData(PowerCellVisuals.ChargeLevel, GetLevel(CurrentCharge / MaxCharge)); - } - } - - private byte GetLevel(float fraction) - { - return (byte) ContentHelpers.RoundToNearestLevels(fraction, 1, SharedPowerCell.PowerCellVisualsLevels); - } - - void IExamine.Examine(FormattedMessage message, bool inDetailsRange) - { - if (inDetailsRange) - { - message.AddMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{CurrentCharge / MaxCharge * 100:F0}"))); - } - } - } - - public enum PowerCellSize - { - Small, - Medium, - Large - } -} diff --git a/Content.Server/PowerCell/Components/PowerCellSlotComponent.cs b/Content.Server/PowerCell/Components/PowerCellSlotComponent.cs deleted file mode 100644 index d8e8cc7216..0000000000 --- a/Content.Server/PowerCell/Components/PowerCellSlotComponent.cs +++ /dev/null @@ -1,221 +0,0 @@ -using System; -using Content.Server.Hands.Components; -using Content.Shared.Audio; -using Content.Shared.Examine; -using Content.Shared.Item; -using Content.Shared.Sound; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Player; -using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; -using Robust.Shared.ViewVariables; - -namespace Content.Server.PowerCell.Components -{ - /// - /// Provides a "battery compartment" that can contain a of the matching - /// . Intended to supplement other components, not very useful by itself. - /// - [RegisterComponent] -#pragma warning disable 618 - public class PowerCellSlotComponent : Component, IExamine, IMapInit -#pragma warning restore 618 - { - [Dependency] private readonly IEntityManager _entities = default!; - - public override string Name => "PowerCellSlot"; - - /// - /// What size of cell fits into this component. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("slotSize")] - public PowerCellSize SlotSize { get; set; } = PowerCellSize.Small; - - /// - /// Can the cell be removed ? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canRemoveCell")] - public bool CanRemoveCell { get; set; } = true; - - /// - /// Should the "Remove cell" verb be displayed on this component? - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("showVerb")] - public bool ShowVerb { get; set; } = true; - - /// - /// String passed to String.Format when showing the description text for this item. - /// String.Format is given a single parameter which is the size letter (S/M/L) of the cells this component uses. - /// Use null to show no text. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("descFormatString")] - public string? DescFormatString { get; set; } = "It uses size {0} power cells."; - - /// - /// File path to a sound file that should be played when the cell is removed. - /// - /// "/Audio/Items/pistol_magout.ogg" - [ViewVariables(VVAccess.ReadWrite)] - [DataField("cellRemoveSound")] - public SoundSpecifier CellRemoveSound { get; set; } = new SoundPathSpecifier("/Audio/Items/pistol_magin.ogg"); - - /// - /// File path to a sound file that should be played when a cell is inserted. - /// - /// "/Audio/Items/pistol_magin.ogg" - [ViewVariables(VVAccess.ReadWrite)] - [DataField("cellInsertSound")] - public SoundSpecifier CellInsertSound { get; set; } = new SoundPathSpecifier("/Audio/Items/pistol_magout.ogg"); - - [ViewVariables] private ContainerSlot _cellContainer = default!; - - [ViewVariables] - public PowerCellComponent? Cell - { - get - { - if (_cellContainer.ContainedEntity == null) return null; - return _entities.TryGetComponent(_cellContainer.ContainedEntity.Value, out PowerCellComponent? cell) ? cell : null; - } - } - - [ViewVariables] public bool HasCell => Cell != null; - - /// - /// True if we don't want a cell inserted during map init. - /// - [DataField("startEmpty")] - private bool _startEmpty = false; - - /// - /// If not null, this cell type will be inserted at MapInit instead of the default Standard cell. - /// - [DataField("startingCellType")] - private string? _startingCellType = null; - - protected override void Initialize() - { - base.Initialize(); - _cellContainer = ContainerHelpers.EnsureContainer(Owner, "cellslot_cell_container", out _); - } - - void IExamine.Examine(FormattedMessage message, bool inDetailsRange) - { - if (!inDetailsRange) return; - string sizeLetter = SlotSize switch - { - PowerCellSize.Small => Loc.GetString("power-cell-slot-component-small-size-shorthand"), - PowerCellSize.Medium => Loc.GetString("power-cell-slot-component-medium-size-shorthand"), - PowerCellSize.Large => Loc.GetString("power-cell-slot-component-large-size-shorthand"), - _ => "???" - }; - if (DescFormatString != null) message.AddMarkup(string.Format(DescFormatString, sizeLetter)); - } - - /// - /// Remove the cell from this component. If a user is specified, the cell will be put in their hands - /// or failing that, at their feet. If no user is specified the cell will be put at the location of - /// the parent of this component. - /// - /// (optional) the user to give the removed cell to. - /// Should be played upon removal? - /// The cell component of the entity that was removed, or null if removal failed. - public PowerCellComponent? EjectCell(EntityUid? user = null, bool playSound = true) - { - var cell = Cell; - if (cell == null || !CanRemoveCell) return null; - if (!_cellContainer.Remove(cell.Owner)) return null; - //Dirty(); - if (user != null) - { - if (!_entities.TryGetComponent(user, out HandsComponent? hands) || !hands.PutInHand(_entities.GetComponent(cell.Owner))) - { - _entities.GetComponent(cell.Owner).Coordinates = _entities.GetComponent(user.Value).Coordinates; - } - } - else - { - _entities.GetComponent(cell.Owner).Coordinates = _entities.GetComponent(Owner).Coordinates; - } - - if (playSound) - { - SoundSystem.Play(Filter.Pvs(Owner), CellRemoveSound.GetSound(), Owner, AudioHelpers.WithVariation(0.125f)); - } - - _entities.EventBus.RaiseLocalEvent(Owner, new PowerCellChangedEvent(true), false); - return cell; - } - - /// - /// Tries to insert the given cell into this component. The cell will be put into the container of this component. - /// - /// The cell to insert. - /// Should be played upon insertion? - /// True if insertion succeeded; false otherwise. - public bool InsertCell(EntityUid cell, bool playSound = true) - { - if (Cell != null) return false; - if (!_entities.HasComponent(cell)) return false; - if (!_entities.TryGetComponent(cell, out var cellComponent)) return false; - if (cellComponent.CellSize != SlotSize) return false; - if (!_cellContainer.Insert(cell)) return false; - //Dirty(); - if (playSound) - { - SoundSystem.Play(Filter.Pvs(Owner), CellInsertSound.GetSound(), Owner, AudioHelpers.WithVariation(0.125f)); - } - - _entities.EventBus.RaiseLocalEvent(Owner, new PowerCellChangedEvent(false), false); - return true; - } - - void IMapInit.MapInit() - { - if (_startEmpty || _cellContainer.ContainedEntity != null) - { - return; - } - - string type; - if (_startingCellType != null) - { - type = _startingCellType; - } - else - { - type = SlotSize switch - { - PowerCellSize.Small => "PowerCellSmallStandard", - PowerCellSize.Medium => "PowerCellMediumStandard", - PowerCellSize.Large => "PowerCellLargeStandard", - _ => throw new ArgumentOutOfRangeException() - }; - } - - var cell = _entities.SpawnEntity(type, _entities.GetComponent(Owner).Coordinates); - _cellContainer.Insert(cell); - } - } - - public class PowerCellChangedEvent : EntityEventArgs - { - /// - /// If true, the cell was ejected; if false, it was inserted. - /// - public bool Ejected { get; } - - public PowerCellChangedEvent(bool ejected) - { - Ejected = ejected; - } - } -} diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs index 422025d370..2bd81447d3 100644 --- a/Content.Server/PowerCell/PowerCellSystem.cs +++ b/Content.Server/PowerCell/PowerCellSystem.cs @@ -1,68 +1,98 @@ +using Content.Server.Administration.Logs; using Content.Server.Chemistry.EntitySystems; -using Content.Server.PowerCell.Components; -using Content.Shared.ActionBlocker; -using Content.Shared.Verbs; -using JetBrains.Annotations; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Power.Components; +using Content.Shared.Database; +using Content.Shared.Examine; +using Content.Shared.PowerCell; +using Content.Shared.PowerCell.Components; +using Content.Shared.Rounding; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; +using System; +using System.Diagnostics.CodeAnalysis; -namespace Content.Server.PowerCell +namespace Content.Server.PowerCell; + +public class PowerCellSystem : SharedPowerCellSystem { - [UsedImplicitly] - public class PowerCellSystem : EntitySystem + [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!; + [Dependency] private readonly ExplosionSystem _explosionSystem = default!; + [Dependency] private readonly AdminLogSystem _logSystem = default!; + + public override void Initialize() { - [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnChargeChanged); + SubscribeLocalEvent(OnSolutionChange); + + SubscribeLocalEvent(OnCellExamined); + } + + private void OnChargeChanged(EntityUid uid, PowerCellComponent component, ChargeChangedEvent args) + { + if (component.IsRigged) { - base.Initialize(); - - SubscribeLocalEvent(OnSolutionChange); - SubscribeLocalEvent(AddEjectVerb); - SubscribeLocalEvent(AddInsertVerb); + Explode(uid); + return; } - // TODO VERBS EJECTABLES Standardize eject/insert verbs into a single system? - private void AddEjectVerb(EntityUid uid, PowerCellSlotComponent component, GetAlternativeVerbsEvent args) - { - if (args.Hands == null || - !args.CanAccess || - !args.CanInteract || - !component.ShowVerb || - !component.HasCell || - !_actionBlockerSystem.CanPickup(args.User)) - return; + if (!TryComp(uid, out BatteryComponent? battery)) + return; - Verb verb = new(); - verb.Text = component.Cell!.Name; - verb.Category = VerbCategory.Eject; - verb.Act = () => component.EjectCell(args.User); - args.Verbs.Add(verb); + if (!TryComp(uid, out AppearanceComponent? appearance)) + return; + + var frac = battery.CurrentCharge / battery.MaxCharge; + var level = (byte) ContentHelpers.RoundToNearestLevels(frac, 1, PowerCellComponent.PowerCellVisualsLevels); + appearance.SetData(PowerCellVisuals.ChargeLevel, level); + } + + private void Explode(EntityUid uid, BatteryComponent? battery = null) + { + _logSystem.Add(LogType.Explosion, LogImpact.High, $"Sabotaged power cell {ToPrettyString(uid)} is exploding"); + + if (!Resolve(uid, ref battery)) + return; + + var heavy = (int) Math.Ceiling(Math.Sqrt(battery.CurrentCharge) / 60); + var light = (int) Math.Ceiling(Math.Sqrt(battery.CurrentCharge) / 30); + + _explosionSystem.SpawnExplosion(uid, 0, heavy, light, light * 2); + QueueDel(uid); + } + + public bool TryGetBatteryFromSlot(EntityUid uid, [NotNullWhen(true)] out BatteryComponent? battery, PowerCellSlotComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + { + battery = null; + return false; } - private void AddInsertVerb(EntityUid uid, PowerCellSlotComponent component, GetInteractionVerbsEvent args) - { - if (args.Using is not {Valid: true} @using || - !args.CanAccess || - !args.CanInteract || - component.HasCell || - !EntityManager.HasComponent(@using) || - !_actionBlockerSystem.CanDrop(args.User)) - return; + return TryComp(component.CellSlot.Item, out battery); + } - Verb verb = new(); - verb.Text = EntityManager.GetComponent(@using).EntityName; - verb.Category = VerbCategory.Insert; - verb.Act = () => component.InsertCell(@using); - args.Verbs.Add(verb); - } + private void OnSolutionChange(EntityUid uid, PowerCellComponent component, SolutionChangedEvent args) + { + component.IsRigged = _solutionsSystem.TryGetSolution(uid, PowerCellComponent.SolutionName, out var solution) + && solution.ContainsReagent("Plasma", out var plasma) + && plasma >= 5; - private void OnSolutionChange(EntityUid uid, PowerCellComponent component, SolutionChangedEvent args) + if (component.IsRigged) { - component.IsRigged = _solutionsSystem.TryGetSolution(uid, PowerCellComponent.SolutionName, out var solution) - && solution.ContainsReagent("Plasma", out var plasma) - && plasma >= 5; + _logSystem.Add(LogType.Explosion, LogImpact.Medium, $"Power cell {ToPrettyString(uid)} has been rigged up to explode when used."); } } + + private void OnCellExamined(EntityUid uid, PowerCellComponent component, ExaminedEvent args) + { + if (!TryComp(uid, out BatteryComponent? battery)) + return; + + var charge = battery.CurrentCharge / battery.MaxCharge * 100; + args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{charge:F0}"))); + } } diff --git a/Content.Server/Stunnable/StunbatonSystem.cs b/Content.Server/Stunnable/StunbatonSystem.cs index 15576ee10b..7c5d728fb1 100644 --- a/Content.Server/Stunnable/StunbatonSystem.cs +++ b/Content.Server/Stunnable/StunbatonSystem.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using Content.Server.PowerCell.Components; +using Content.Server.PowerCell; using Content.Server.Speech.EntitySystems; using Content.Server.Stunnable.Components; using Content.Server.Weapon.Melee; @@ -11,6 +11,7 @@ using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Jittering; using Content.Shared.Popups; +using Content.Shared.PowerCell.Components; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; using Content.Shared.Throwing; @@ -29,6 +30,7 @@ namespace Content.Server.Stunnable [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly StutteringSystem _stutteringSystem = default!; [Dependency] private readonly SharedJitteringSystem _jitterSystem = default!; + [Dependency] private readonly PowerCellSystem _cellSystem = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; public override void Initialize() @@ -40,7 +42,6 @@ namespace Content.Server.Stunnable SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent(OnThrowCollide); SubscribeLocalEvent(OnPowerCellChanged); - SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnExamined); } @@ -49,7 +50,7 @@ namespace Content.Server.Stunnable if (!comp.Activated || !args.HitEntities.Any()) return; - if (!EntityManager.TryGetComponent(uid, out var slot) || slot.Cell == null || !slot.Cell.TryUseCharge(comp.EnergyPerUse)) + if (!_cellSystem.TryGetBatteryFromSlot(uid, out var battery) || !battery.TryUseCharge(comp.EnergyPerUse)) return; foreach (EntityUid entity in args.HitEntities) @@ -63,7 +64,7 @@ namespace Content.Server.Stunnable if (!comp.Activated) return; - if (!EntityManager.TryGetComponent(uid, out var slot) || slot.Cell == null || !slot.Cell.TryUseCharge(comp.EnergyPerUse)) + if (!_cellSystem.TryGetBatteryFromSlot(uid, out var battery) || !battery.TryUseCharge(comp.EnergyPerUse)) return; args.CanInteract = true; @@ -87,8 +88,11 @@ namespace Content.Server.Stunnable private void OnThrowCollide(EntityUid uid, StunbatonComponent comp, ThrowDoHitEvent args) { - if (!EntityManager.TryGetComponent(uid, out var slot)) return; - if (!comp.Activated || slot.Cell == null || !slot.Cell.TryUseCharge(comp.EnergyPerUse)) return; + if (!comp.Activated) + return; + + if (!_cellSystem.TryGetBatteryFromSlot(uid, out var battery) || !battery.TryUseCharge(comp.EnergyPerUse)) + return; StunEntity(args.Target, comp); } @@ -101,15 +105,6 @@ namespace Content.Server.Stunnable } } - private void OnInteractUsing(EntityUid uid, StunbatonComponent comp, InteractUsingEvent args) - { - if (!Get().CanInteract(args.User)) - return; - - if (EntityManager.TryGetComponent(uid, out var cellslot)) - cellslot.InsertCell(args.Used); - } - private void OnExamined(EntityUid uid, StunbatonComponent comp, ExaminedEvent args) { var msg = comp.Activated @@ -144,7 +139,7 @@ namespace Content.Server.Stunnable _jitterSystem.DoJitter(entity, slowdownTime, true, status:status); _stutteringSystem.DoStutter(entity, slowdownTime, true, status); - if (!EntityManager.TryGetComponent(comp.Owner, out var slot) || slot.Cell == null || !(slot.Cell.CurrentCharge < comp.EnergyPerUse)) + if (!_cellSystem.TryGetBatteryFromSlot(comp.Owner, out var battery) || !(battery.CurrentCharge < comp.EnergyPerUse)) return; SoundSystem.Play(Filter.Pvs(comp.Owner), comp.SparksSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f)); @@ -183,14 +178,14 @@ namespace Content.Server.Stunnable if (!EntityManager.TryGetComponent(comp.Owner, out var slot)) return; - if (slot.Cell == null) + if (!_cellSystem.TryGetBatteryFromSlot(comp.Owner, out var battery)) { SoundSystem.Play(playerFilter, comp.TurnOnFailSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f)); user.PopupMessage(Loc.GetString("comp-stunbaton-activated-missing-cell")); return; } - if (slot.Cell != null && slot.Cell.CurrentCharge < comp.EnergyPerUse) + if (battery.CurrentCharge < comp.EnergyPerUse) { SoundSystem.Play(playerFilter, comp.TurnOnFailSound.GetSound(), comp.Owner, AudioHelpers.WithVariation(0.25f)); user.PopupMessage(Loc.GetString("comp-stunbaton-activated-dead-cell")); diff --git a/Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs b/Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs index 60013c7361..8073ceb929 100644 --- a/Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs +++ b/Content.Server/Weapon/Ranged/Barrels/BarrelSystem.cs @@ -1,13 +1,11 @@ -using Content.Server.Power.Components; using Content.Server.Weapon.Ranged.Barrels.Components; using Content.Shared.ActionBlocker; using Content.Shared.Popups; +using Content.Shared.PowerCell.Components; using Content.Shared.Verbs; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -using System; namespace Content.Server.Weapon.Ranged.Barrels { @@ -18,11 +16,9 @@ namespace Content.Server.Weapon.Ranged.Barrels public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(AddSpinVerb); - SubscribeLocalEvent(OnCellSlotUpdated); - SubscribeLocalEvent(OnCellSlotUpdated); + SubscribeLocalEvent(OnCellSlotUpdated); SubscribeLocalEvent(AddToggleBoltVerb); @@ -30,10 +26,9 @@ namespace Content.Server.Weapon.Ranged.Barrels SubscribeLocalEvent(AddEjectMagazineVerb); } - private void OnCellSlotUpdated(EntityUid uid, ServerBatteryBarrelComponent component, ContainerModifiedMessage args) + private void OnCellSlotUpdated(EntityUid uid, ServerBatteryBarrelComponent component, PowerCellChangedEvent args) { - if (args.Container.ID == component.CellSlot.ID) - component.UpdateAppearance(); + component.UpdateAppearance(); } private void AddSpinVerb(EntityUid uid, RevolverBarrelComponent component, GetAlternativeVerbsEvent args) diff --git a/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs b/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs index 2a0d1cb7cd..295ba5af4e 100644 --- a/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs +++ b/Content.Server/Weapon/Ranged/Barrels/Components/ServerBatteryBarrelComponent.cs @@ -1,15 +1,12 @@ using System; -using Content.Server.Power.Components; +using Content.Server.PowerCell; using Content.Server.Projectiles.Components; -using Content.Shared.Containers.ItemSlots; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Map; -using Robust.Shared.Player; -using Robust.Shared.Players; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -25,9 +22,6 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components public override string Name => "BatteryBarrel"; - [DataField("cellSlot", required: true)] - public ItemSlot CellSlot = new(); - // The minimum change we need before we can fire [DataField("lowerChargeLimit")] [ViewVariables] private float _lowerChargeLimit = 10; @@ -37,19 +31,19 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components [DataField("ammoPrototype", customTypeSerializer:typeof(PrototypeIdSerializer))] [ViewVariables] private string? _ammoPrototype; - public BatteryComponent? PowerCell => _entities.GetComponentOrNull(CellSlot.Item); private ContainerSlot _ammoContainer = default!; public override int ShotsLeft { get { - if (CellSlot.Item is not {} powerCell) + + if (!EntitySystem.Get().TryGetBatteryFromSlot(Owner, out var battery)) { return 0; } - return (int) Math.Ceiling(_entities.GetComponent(powerCell).CurrentCharge / _baseFireCost); + return (int) Math.Ceiling(battery.CurrentCharge / _baseFireCost); } } @@ -57,12 +51,12 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components { get { - if (CellSlot.Item is not {} powerCell) + if (!EntitySystem.Get().TryGetBatteryFromSlot(Owner, out var battery)) { return 0; } - return (int) Math.Ceiling(_entities.GetComponent(powerCell).MaxCharge / _baseFireCost); + return (int) Math.Ceiling(battery.MaxCharge / _baseFireCost); } } @@ -81,8 +75,6 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components { base.Initialize(); - EntitySystem.Get().AddItemSlot(Owner, $"{Name}-powercell-container", CellSlot); - if (_ammoPrototype != null) { _ammoContainer = Owner.EnsureContainer($"{Name}-ammo-container"); @@ -95,12 +87,6 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components Dirty(); } - protected override void OnRemove() - { - base.OnRemove(); - EntitySystem.Get().RemoveItemSlot(Owner, CellSlot); - } - protected override void Startup() { base.Startup(); @@ -109,7 +95,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components public void UpdateAppearance() { - _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, CellSlot.HasItem); + _appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, EntitySystem.Get().TryGetBatteryFromSlot(Owner, out _)); _appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft); _appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity); Dirty(); @@ -131,14 +117,9 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components public override EntityUid? TakeProjectile(EntityCoordinates spawnAt) { - var powerCellEntity = CellSlot.Item; - - if (powerCellEntity == null) - { + if (!EntitySystem.Get().TryGetBatteryFromSlot(Owner, out var capacitor)) return null; - } - var capacitor = _entities.GetComponent(powerCellEntity.Value); if (capacitor.CurrentCharge < _lowerChargeLimit) { return null; diff --git a/Content.Server/Weapon/WeaponCapacitorChargerComponent.cs b/Content.Server/Weapon/WeaponCapacitorChargerComponent.cs deleted file mode 100644 index 3f0563ee03..0000000000 --- a/Content.Server/Weapon/WeaponCapacitorChargerComponent.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Content.Server.Power.Components; -using Content.Server.PowerCell.Components; -using Content.Server.Weapon.Ranged.Barrels.Components; -using Content.Shared.Interaction; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.Weapon -{ - /// - /// Recharges the battery in a . - /// - [RegisterComponent] - [ComponentReference(typeof(IActivate))] - [ComponentReference(typeof(BaseCharger))] - public sealed class WeaponCapacitorChargerComponent : BaseCharger - { - [Dependency] private readonly IEntityManager _entMan = default!; - - public override string Name => "WeaponCapacitorCharger"; - - public override bool IsEntityCompatible(EntityUid entity) - { - return _entMan.TryGetComponent(entity, out ServerBatteryBarrelComponent? battery) && battery.PowerCell != null || - _entMan.TryGetComponent(entity, out PowerCellSlotComponent? slot) && slot.HasCell; - } - - protected override BatteryComponent? GetBatteryFrom(EntityUid entity) - { - if (_entMan.TryGetComponent(entity, out PowerCellSlotComponent? slot)) - { - if (slot.Cell != null) - { - return slot.Cell; - } - } - - if (_entMan.TryGetComponent(entity, out ServerBatteryBarrelComponent? battery)) - { - if (battery.PowerCell != null) - { - return battery.PowerCell; - } - } - - return null; - } - } -} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs index 95a32b5a10..3def5a2e54 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs @@ -75,12 +75,10 @@ namespace Content.Shared.Containers.ItemSlots public EntityWhitelist? Whitelist; [DataField("insertSound")] - public SoundSpecifier? InsertSound; - // maybe default to /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg ?? + public SoundSpecifier InsertSound = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/revolver_magin.ogg"); [DataField("ejectSound")] - public SoundSpecifier? EjectSound; - // maybe default to /Audio/Machines/id_swipe.ogg? + public SoundSpecifier EjectSound = new SoundPathSpecifier("/Audio/Weapons/Guns/MagOut/revolver_magout.ogg"); /// /// Options used for playing the insert/eject sounds. @@ -98,6 +96,9 @@ namespace Content.Shared.Containers.ItemSlots [DataField("name")] public string Name = string.Empty; + /// + /// The entity prototype that is spawned into this slot on map init. + /// [DataField("startingItem", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? StartingItem; @@ -136,15 +137,15 @@ namespace Content.Shared.Containers.ItemSlots public bool EjectOnUse = false; /// - /// Override the insert verb text. Defaults to [insert category] -> [item-name]. If not null, the verb will - /// not be given a category. + /// Override the insert verb text. Defaults to using the slot's name (if specified) or the name of the + /// targeted item. If specified, the verb will not be added to the default insert verb category. /// [DataField("insertVerbText")] public string? InsertVerbText; /// - /// Override the insert verb text. Defaults to [eject category] -> [item-name]. If not null, the verb will - /// not be given a category. + /// Override the eject verb text. Defaults to using the slot's name (if specified) or the name of the + /// targeted item. If specified, the verb will not be added to the default eject verb category /// [DataField("ejectVerbText")] public string? EjectVerbText; diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 7097355925..3e2fec8c0a 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -242,10 +242,7 @@ namespace Content.Shared.Containers.ItemSlots return false; } - // We should also check ContainerSlot.CanInsert, but that prevents swapping interactions. Given that - // ContainerSlot.CanInsert gets called when the item is actually inserted anyways, we can just get away with - // fudging CanInsert and not performing those checks. - return true; + return slot.ContainerSlot.CanInsertIfEmpty(usedUid, EntityManager); } /// @@ -302,6 +299,15 @@ namespace Content.Shared.Containers.ItemSlots #endregion #region Eject + + public bool CanEject(ItemSlot slot) + { + if (slot.Locked || slot.Item == null) + return false; + + return slot.ContainerSlot.CanRemove(slot.Item.Value, EntityManager); + } + /// /// Eject an item into a slot. This does not perform checks (e.g., is the slot locked?), so you should /// probably just use instead. @@ -324,11 +330,11 @@ namespace Content.Shared.Containers.ItemSlots { item = null; - if (slot.Locked || slot.Item == null) + if (!CanEject(slot)) return false; item = slot.Item; - Eject(uid, slot, item.Value, user, excludeUserAudio); + Eject(uid, slot, item!.Value, user, excludeUserAudio); return true; } @@ -381,7 +387,7 @@ namespace Content.Shared.Containers.ItemSlots foreach (var slot in itemSlots.Slots.Values) { - if (slot.Locked || !slot.HasItem) + if (!CanEject(slot)) continue; if (slot.EjectOnInteract) @@ -421,7 +427,7 @@ namespace Content.Shared.Containers.ItemSlots { foreach (var slot in itemSlots.Slots.Values) { - if (!slot.EjectOnInteract || slot.Locked || !slot.HasItem) + if (!slot.EjectOnInteract || !CanEject(slot)) continue; var verbSubject = slot.Name != string.Empty diff --git a/Content.Shared/Light/Component/SharedHandheldLightComponent.cs b/Content.Shared/Light/Component/SharedHandheldLightComponent.cs index 54f218912a..e8a309ef28 100644 --- a/Content.Shared/Light/Component/SharedHandheldLightComponent.cs +++ b/Content.Shared/Light/Component/SharedHandheldLightComponent.cs @@ -9,8 +9,6 @@ namespace Content.Shared.Light.Component [ComponentProtoName("HandheldLight")] public abstract class SharedHandheldLightComponent : Robust.Shared.GameObjects.Component { - protected abstract bool HasCell { get; } - public const int StatusLevels = 6; [Serializable, NetSerializable] diff --git a/Content.Shared/PowerCell/Components/PowerCellComponent.cs b/Content.Shared/PowerCell/Components/PowerCellComponent.cs new file mode 100644 index 0000000000..f045281b58 --- /dev/null +++ b/Content.Shared/PowerCell/Components/PowerCellComponent.cs @@ -0,0 +1,41 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.PowerCell; + +/// +/// This component enables power-cell related interactions (e.g., entity white-lists, cell sizes, examine, rigging). +/// The actual power functionality is provided by the server-side BatteryComponent. +/// +[NetworkedComponent] +[RegisterComponent] +[ComponentProtoName("PowerCell")] +public sealed class PowerCellComponent : Component +{ + public const string SolutionName = "powerCell"; + public const int PowerCellVisualsLevels = 4; + + [DataField("cellSize")] + public PowerCellSize CellSize = PowerCellSize.Small; + + // Not networked to clients + [ViewVariables(VVAccess.ReadWrite)] + public bool IsRigged { get; set; } +} + +public enum PowerCellSize +{ + Small = 0, + Medium = 1, + Large = 2 +} + +[Serializable, NetSerializable] +public enum PowerCellVisuals +{ + ChargeLevel +} diff --git a/Content.Shared/PowerCell/Components/PowerCellSlotComponent.cs b/Content.Shared/PowerCell/Components/PowerCellSlotComponent.cs new file mode 100644 index 0000000000..6bb4742ca0 --- /dev/null +++ b/Content.Shared/PowerCell/Components/PowerCellSlotComponent.cs @@ -0,0 +1,75 @@ +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.PowerCell.Components; + +[RegisterComponent] +public sealed class PowerCellSlotComponent : Component +{ + public override string Name => "PowerCellSlot"; + + /// + /// What size of cell fits into this component. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("slotSize")] + public PowerCellSize SlotSize { get; set; } = PowerCellSize.Small; + + /// + /// The actual item-slot that contains the cell. Allows all the interaction logic to be handled by . + /// + /// + /// Given that needs to verify that a given cell has the correct cell-size before + /// inserting anyways, there is no need to specify a separate entity whitelist. In this slot's yaml definition. + /// + [DataField("cellSlot")] + public ItemSlot CellSlot = new(); + + /// + /// Name of the item-slot used to store cells. Determines the eject/insert verb text. E.g., "Eject > Power cell". + /// + /// + /// This is simply used provide a default value for . If this string is empty or + /// whitespace, the verb will instead use the full name of any cell (e.g., "eject > small super-capacity power + /// cell"). + /// + [DataField("slotName")] + public readonly string SlotName = "power-cell-slot-component-slot-name-default"; // gets Loc.GetString()-ed by ItemSlotsSystem + + /// + /// True if we don't want a cell inserted during map init. If a starting item is defined + /// in the yaml definition, that always takes precedence. + /// + /// + /// If false, the cell will start with a standard cell with a matching cell-size. + /// + [DataField("startEmpty")] + public bool StartEmpty = false; + + /// + /// Descriptive text to add to add when examining an entity with a cell slot. If empty or whitespace, will not add + /// any text. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("descFormatString")] + public string? DescFormatString { get; set; } = "power-cell-slot-component-description-default"; + + /// + /// Can this entity be inserted directly into a charging station? If false, you need to manually remove the power + /// cell and recharge it separately. + /// + [DataField("fitsInCharger")] + public bool FitsInCharger = true; +} + +public class PowerCellChangedEvent : EntityEventArgs +{ + public readonly bool Ejected; + + public PowerCellChangedEvent(bool ejected) + { + Ejected = ejected; + } +} diff --git a/Content.Shared/PowerCell/SharedPowerCell.cs b/Content.Shared/PowerCell/SharedPowerCell.cs deleted file mode 100644 index b90b83bcbb..0000000000 --- a/Content.Shared/PowerCell/SharedPowerCell.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Robust.Shared.Serialization; - -namespace Content.Shared.PowerCell -{ - public static class SharedPowerCell - { - public const int PowerCellVisualsLevels = 4; - } - - [Serializable, NetSerializable] - public enum PowerCellVisuals - { - ChargeLevel - } -} diff --git a/Content.Shared/PowerCell/SharedPowerCellSystem.cs b/Content.Shared/PowerCell/SharedPowerCellSystem.cs new file mode 100644 index 0000000000..f68eeef4b9 --- /dev/null +++ b/Content.Shared/PowerCell/SharedPowerCellSystem.cs @@ -0,0 +1,109 @@ +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Examine; +using Content.Shared.PowerCell.Components; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using System; + +namespace Content.Shared.PowerCell; + +public abstract class SharedPowerCellSystem : EntitySystem +{ + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCellSlotInit); + SubscribeLocalEvent(OnCellSlotRemove); + + SubscribeLocalEvent(OnSlotExamined); + + SubscribeLocalEvent(OnCellInserted); + SubscribeLocalEvent(OnCellRemoved); + SubscribeLocalEvent(OnCellInsertAttempt); + } + + private void OnCellInsertAttempt(EntityUid uid, PowerCellSlotComponent component, ContainerIsInsertingAttemptEvent args) + { + if (!component.Initialized) + return; + + if (args.Container.ID != component.CellSlot.ID) + return; + + if (!TryComp(args.EntityUid, out PowerCellComponent? cell) || cell.CellSize != component.SlotSize) + { + args.Cancel(); + } + } + + private void OnCellInserted(EntityUid uid, PowerCellSlotComponent component, EntInsertedIntoContainerMessage args) + { + if (!component.Initialized) + return; + + if (args.Container.ID != component.CellSlot.ID) + return; + + RaiseLocalEvent(uid, new PowerCellChangedEvent(false), false); + } + + private void OnCellRemoved(EntityUid uid, PowerCellSlotComponent component, EntRemovedFromContainerMessage args) + { + if (args.Container.ID != component.CellSlot.ID) + return; + + RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false); + } + + private void OnCellSlotInit(EntityUid uid, PowerCellSlotComponent component, ComponentInit args) + { + _itemSlotsSystem.AddItemSlot(uid, "cellslot_cell_container", component.CellSlot); + + if (string.IsNullOrWhiteSpace(component.CellSlot.Name) && + !string.IsNullOrWhiteSpace(component.SlotName)) + { + component.CellSlot.Name = component.SlotName; + } + + if (component.StartEmpty) + return; + + if (!string.IsNullOrWhiteSpace(component.CellSlot.StartingItem)) + return; + + // set default starting cell based on cell-type + component.CellSlot.StartingItem = component.SlotSize switch + { + PowerCellSize.Small => "PowerCellSmallStandard", + PowerCellSize.Medium => "PowerCellMediumStandard", + PowerCellSize.Large => "PowerCellLargeStandard", + _ => throw new ArgumentOutOfRangeException() + }; + } + + private void OnCellSlotRemove(EntityUid uid, PowerCellSlotComponent component, ComponentRemove args) + { + _itemSlotsSystem.RemoveItemSlot(uid, component.CellSlot); + } + + private void OnSlotExamined(EntityUid uid, PowerCellSlotComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange || string.IsNullOrWhiteSpace(component.DescFormatString)) + return; + + var sizeText = Loc.GetString(component.SlotSize switch + { + PowerCellSize.Small => "power-cell-slot-component-description-size-small", + PowerCellSize.Medium => "power-cell-slot-component-description-size-medium", + PowerCellSize.Large => "power-cell-slot-component-description-size-large", + _ => "???" + }); + + args.PushMarkup(Loc.GetString(component.DescFormatString, ("size", sizeText))); + } +} diff --git a/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl b/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl index d7547b653f..a5357eca44 100644 --- a/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl +++ b/Resources/Locale/en-US/power-cell/components/power-cell-slot-component.ftl @@ -1,3 +1,8 @@ -power-cell-slot-component-small-size-shorthand = S -power-cell-slot-component-medium-size-shorthand = M -power-cell-slot-component-large-size-shorthand = L \ No newline at end of file +# Default examine descriptions +power-cell-slot-component-description-default = It uses {$size} power cells. +power-cell-slot-component-description-size-small = small +power-cell-slot-component-description-size-medium = medium-sized +power-cell-slot-component-description-size-large = large + +# Verbs +power-cell-slot-component-slot-name-default = Power cell diff --git a/Resources/Locale/en-US/power/components/base-charger.ftl b/Resources/Locale/en-US/power/components/base-charger.ftl deleted file mode 100644 index d30fbbbf0e..0000000000 --- a/Resources/Locale/en-US/power/components/base-charger.ftl +++ /dev/null @@ -1 +0,0 @@ -base-charger-on-interact-using-fail = Unable to insert capacitor \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index 69c29f9a49..9f711042a0 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -69,6 +69,6 @@ # so hardsuit helmet just have small battery inside - type: HandheldLight - type: PowerCellSlot - startingCellType: PowerCellHardsuitHelmet - canRemoveCell: false - showVerb: false + cellSlot: + startingItem: PowerCellHardsuitHelmet # self recharging + locked: true # no need to recharge manually \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml index 0126d5a4de..66e1b9b97b 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardhats.yml @@ -22,7 +22,8 @@ actions: - actionType: ToggleLight - type: PowerCellSlot - startingCellType: PowerCellSmallHigh + cellSlot: + startingItem: PowerCellSmallHigh - type: entity parent: ClothingHeadHatHardhatBase diff --git a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml index 44bdabc183..daaaef9c66 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml @@ -5,6 +5,7 @@ abstract: true components: - type: HandheldLight + - type: PowerCellSlot - type: ItemActions actions: - actionType: ToggleLight diff --git a/Resources/Prototypes/Entities/Objects/Power/powercells.yml b/Resources/Prototypes/Entities/Objects/Power/powercells.yml index 1cd43ba94e..1f24bed06b 100644 --- a/Resources/Prototypes/Entities/Objects/Power/powercells.yml +++ b/Resources/Prototypes/Entities/Objects/Power/powercells.yml @@ -2,10 +2,11 @@ # Power cells - type: entity - id: PowerCellBase + id: PowerCellSmallBase abstract: true parent: BaseItem components: + - type: Battery - type: PowerCell - type: Sprite netsync: false @@ -23,18 +24,10 @@ - ReagentId: Licoxide Quantity: 5 -- type: entity - id: PowerCellSmallBase - abstract: true - parent: PowerCellBase - components: - - type: PowerCell - cellSize: Small - - type: entity id: PowerCellMediumBase abstract: true - parent: PowerCellBase + parent: PowerCellSmallBase components: - type: PowerCell cellSize: Medium @@ -42,7 +35,7 @@ - type: entity id: PowerCellLargeBase abstract: true - parent: PowerCellBase + parent: PowerCellSmallBase components: - type: PowerCell cellSize: Large @@ -57,10 +50,9 @@ sprite: Objects/Power/PowerCells/potato_battery.rsi layers: - state: potato_battery - - type: PowerCell + - type: Battery maxCharge: 360 startingCharge: 360 - updateVisual: false - type: entity name: small standard power cell @@ -72,7 +64,7 @@ sprite: Objects/Power/PowerCells/power_cell_small_st.rsi layers: - state: s_st - - type: PowerCell + - type: Battery maxCharge: 360 startingCharge: 360 - type: Appearance @@ -80,7 +72,6 @@ - type: PowerCellVisualizer prefix: s_st - - type: entity name: small high-capacity power cell description: A rechargeable standardized power cell, size S. This is the popular and reliable version. @@ -91,7 +82,7 @@ sprite: Objects/Power/PowerCells/power_cell_small_hi.rsi layers: - state: s_hi - - type: PowerCell + - type: Battery maxCharge: 720 startingCharge: 720 - type: Appearance @@ -99,7 +90,6 @@ - type: PowerCellVisualizer prefix: s_hi - - type: entity name: small super-capacity power cell description: A rechargeable standardized power cell, size S. This premium high-capacity brand stores up to 50% more energy than the competition. @@ -110,7 +100,7 @@ sprite: Objects/Power/PowerCells/power_cell_small_sup.rsi layers: - state: s_sup - - type: PowerCell + - type: Battery maxCharge: 1080 startingCharge: 1080 - type: Appearance @@ -129,7 +119,7 @@ sprite: Objects/Power/PowerCells/power_cell_small_hy.rsi layers: - state: s_hy - - type: PowerCell + - type: Battery maxCharge: 1800 startingCharge: 1800 - type: Appearance @@ -147,7 +137,7 @@ sprite: Objects/Power/PowerCells/power_cell_small_autorecharge.rsi layers: - state: s_ar - - type: PowerCell + - type: Battery maxCharge: 50 startingCharge: 50 - type: BatterySelfRecharger @@ -168,7 +158,7 @@ sprite: Objects/Power/PowerCells/power_cell_small_autorecharge.rsi layers: - state: s_ar - - type: PowerCell + - type: Battery maxCharge: 600 #lights drain 3/s but recharge of 2 makes this 1/s. Therefore 600 is 10 minutes of light. startingCharge: 600 - type: BatterySelfRecharger @@ -179,7 +169,6 @@ - type: PowerCellVisualizer prefix: s_ar - - type: entity name: medium standard power cell description: A rechargeable standardized power cell, size M. This is the cheapest kind you can find. @@ -190,7 +179,7 @@ sprite: Objects/Power/PowerCells/power_cell_medium_st.rsi layers: - state: m_st - - type: PowerCell + - type: Battery maxCharge: 2160 startingCharge: 2160 - type: Appearance @@ -208,10 +197,9 @@ sprite: Objects/Power/PowerCells/power_cell_medium_hi.rsi layers: - state: m_hi - - type: PowerCell + - type: Battery maxCharge: 2880 startingCharge: 2880 - powerCellSize: Medium - type: Appearance visuals: - type: PowerCellVisualizer @@ -227,7 +215,7 @@ sprite: Objects/Power/PowerCells/power_cell_medium_sup.rsi layers: - state: m_sup - - type: PowerCell + - type: Battery maxCharge: 3600 startingCharge: 3600 - type: Appearance @@ -245,7 +233,7 @@ sprite: Objects/Power/PowerCells/power_cell_medium_hy.rsi layers: - state: m_hy - - type: PowerCell + - type: Battery maxCharge: 5400 startingCharge: 5400 - type: Appearance @@ -263,7 +251,7 @@ sprite: Objects/Power/PowerCells/power_cell_large_st.rsi layers: - state: l_st - - type: PowerCell + - type: Battery maxCharge: 9000 startingCharge: 9000 - type: Appearance @@ -281,7 +269,7 @@ sprite: Objects/Power/PowerCells/power_cell_large_hi.rsi layers: - state: l_hi - - type: PowerCell + - type: Battery maxCharge: 18000 startingCharge: 18000 - type: Appearance @@ -299,7 +287,7 @@ sprite: Objects/Power/PowerCells/power_cell_large_sup.rsi layers: - state: l_sup - - type: PowerCell + - type: Battery maxCharge: 54000 startingCharge: 54000 - type: Appearance @@ -317,7 +305,7 @@ sprite: Objects/Power/PowerCells/power_cell_large_hy.rsi layers: - state: l_hy - - type: PowerCell + - type: Battery maxCharge: 72000 startingCharge: 72000 - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml index 0c56ec313c..77902ca7bc 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml @@ -12,7 +12,8 @@ actions: - actionType: ToggleLight - type: PowerCellSlot - startingCellType: PowerCellSmallHigh + cellSlot: + startingItem: PowerCellSmallHigh - type: Sprite sprite: Objects/Tools/flashlight.rsi layers: @@ -39,7 +40,8 @@ description: A robust flashlight used by security. components: - type: PowerCellSlot - startingCellType: PowerCellSmallSuper + cellSlot: + startingItem: PowerCellSmallSuper - type: Sprite sprite: Objects/Tools/seclite.rsi layers: diff --git a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml index 7790ce2900..c644120d0e 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/lantern.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/lantern.yml @@ -27,7 +27,8 @@ visuals: - type: LanternVisualizer - type: PowerCellSlot - startingCellType: PowerCellSmallHigh + cellSlot: + startingItem: PowerCellSmallHigh - type: entity name: extra-bright lantern diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index ccc75b79de..3e8a8fed69 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -16,16 +16,11 @@ - Single soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser.ogg + - type: PowerCellSlot cellSlot: ejectOnUse: true - insertSound: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg - ejectSound: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg soundOptions: volume: -2 - startingItem: PowerCellSmallStandard - whitelist: - components: - - Battery - type: Appearance visuals: - type: MagVisualizer @@ -148,16 +143,12 @@ ammoPrototype: RedHeavyLaser soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg + - type: PowerCellSlot cellSlot: ejectOnUse: true - insertSound: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg - ejectSound: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg soundOptions: volume: -2 startingItem: PowerCellSmallSuper - whitelist: - components: - - Battery - type: entity name: x-ray cannon @@ -182,16 +173,12 @@ ammoPrototype: XrayLaser soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser3.ogg + - type: PowerCellSlot cellSlot: ejectOnUse: true - insertSound: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg - ejectSound: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg soundOptions: volume: -2 startingItem: PowerCellSmallSuper - whitelist: - components: - - Battery - type: Appearance visuals: - type: MagVisualizer @@ -231,16 +218,12 @@ ammoPrototype: BulletTaser soundGunshot: path: /Audio/Weapons/Guns/Gunshots/taser.ogg + - type: PowerCellSlot + descFormatString : "" # empty string for no examine-text (cell is not ejectable) cellSlot: - insertSound: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg - ejectSound: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg soundOptions: volume: -2 locked: true - startingItem: PowerCellSmallStandard - whitelist: - components: - - Battery - type: Appearance visuals: - type: MagVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index ab4859b354..cfc7e05482 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -17,7 +17,8 @@ arc: default - type: PowerCellSlot slotSize: Medium - startingCellType: PowerCellMediumHigh + cellSlot: + startingItem: PowerCellMediumHigh - type: ItemCooldown - type: Clothing sprite: Objects/Weapons/Melee/stunbaton.rsi diff --git a/Resources/Prototypes/Entities/Structures/Power/chargers.yml b/Resources/Prototypes/Entities/Structures/Power/chargers.yml index 79ee567ff8..cccd7c0a4c 100644 --- a/Resources/Prototypes/Entities/Structures/Power/chargers.yml +++ b/Resources/Prototypes/Entities/Structures/Power/chargers.yml @@ -9,8 +9,13 @@ - type: Icon sprite: Structures/Power/cell_recharger.rsi state: empty - - type: PowerCellCharger - transfer_efficiency: 0.85 + - type: Charger + chargerSlot: + ejectOnInteract: true + name: Power cell # used for verbs: "Eject > Power cell " + whitelist: + components: + - PowerCell - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: Appearance @@ -23,6 +28,7 @@ - type: entity name: recharger id: WeaponCapacitorRecharger + parent: PowerCellRecharger components: - type: Sprite netsync: false @@ -31,20 +37,18 @@ - type: Icon sprite: Structures/Power/recharger.rsi state: empty - - type: WeaponCapacitorCharger - transfer_efficiency: 0.85 - - type: ApcPowerReceiver - - type: ExtensionCableReceiver - - type: Appearance - visuals: - - type: PowerChargerVisualizer - - type: Anchorable - - type: Clickable - - type: InteractionOutline + - type: Charger + chargerSlot: + ejectOnInteract: true + whitelist: + components: + - PowerCell + - PowerCellSlot - type: entity name: wall recharger id: WallWeaponCapacitorRecharger + parent: PowerCellRecharger components: - type: Sprite netsync: false @@ -53,12 +57,11 @@ - type: Icon sprite: Structures/Power/wall_recharger.rsi state: empty - - type: WeaponCapacitorCharger - transfer_efficiency: 0.95 - - type: ApcPowerReceiver - - type: ExtensionCableReceiver - - type: Appearance - visuals: - - type: PowerChargerVisualizer - - type: Clickable - - type: InteractionOutline + - type: Charger + transferEfficiency: 0.95 + chargerSlot: + ejectOnInteract: true + whitelist: + components: + - PowerCell + - PowerCellSlot \ No newline at end of file