Power Cell Refactor (#5943)

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2022-01-05 17:20:25 +13:00
committed by GitHub
parent 4eddefdda1
commit 0aa4f9efbe
37 changed files with 673 additions and 987 deletions

View File

@@ -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();
}
}
}

View File

@@ -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 { }
}

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

View File

@@ -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);
}
}
}

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