Power Cell Refactor (#5943)
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
This commit is contained in:
@@ -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<ApcPowerReceiverComponent>();
|
||||
Container = ContainerHelpers.EnsureContainer<ContainerSlot>(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<bool> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will remove the item directly into the user's hand / floor
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
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<SharedItemComponent>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the supplied entity should fit into the charger.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
/// <summary>
|
||||
@@ -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 { }
|
||||
}
|
||||
|
||||
121
Content.Server/Power/Components/ChargerComponent.cs
Normal file
121
Content.Server/Power/Components/ChargerComponent.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<PowerCellChargerComponent, GetAlternativeVerbsEvent>(AddEjectVerb);
|
||||
SubscribeLocalEvent<PowerCellChargerComponent, GetInteractionVerbsEvent>(AddInsertVerb);
|
||||
SubscribeLocalEvent<WeaponCapacitorChargerComponent, GetAlternativeVerbsEvent>(AddEjectVerb);
|
||||
SubscribeLocalEvent<WeaponCapacitorChargerComponent, GetInteractionVerbsEvent>(AddInsertVerb);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in EntityManager.EntityQuery<BaseCharger>())
|
||||
{
|
||||
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<MetaDataComponent>(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<MetaDataComponent>(@using).EntityName;
|
||||
verb.Category = VerbCategory.Insert;
|
||||
verb.Act = () => component.TryInsertItem(@using);
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Content.Server/Power/EntitySystems/ChargerSystem.cs
Normal file
97
Content.Server/Power/EntitySystems/ChargerSystem.cs
Normal file
@@ -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<ChargerComponent, ComponentInit>(OnChargerInit);
|
||||
SubscribeLocalEvent<ChargerComponent, ComponentRemove>(OnChargerRemove);
|
||||
|
||||
SubscribeLocalEvent<ChargerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
|
||||
SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<ChargerComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
foreach (var comp in EntityManager.EntityQuery<ChargerComponent>())
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the entity being inserted is actually rechargeable.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user