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

@@ -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");
/// <summary>
/// Options used for playing the insert/eject sounds.
@@ -98,6 +96,9 @@ namespace Content.Shared.Containers.ItemSlots
[DataField("name")]
public string Name = string.Empty;
/// <summary>
/// The entity prototype that is spawned into this slot on map init.
/// </summary>
[DataField("startingItem", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? StartingItem;
@@ -136,15 +137,15 @@ namespace Content.Shared.Containers.ItemSlots
public bool EjectOnUse = false;
/// <summary>
/// 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.
/// </summary>
[DataField("insertVerbText")]
public string? InsertVerbText;
/// <summary>
/// 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
/// </summary>
[DataField("ejectVerbText")]
public string? EjectVerbText;

View File

@@ -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);
}
/// <summary>
@@ -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);
}
/// <summary>
/// Eject an item into a slot. This does not perform checks (e.g., is the slot locked?), so you should
/// probably just use <see cref="TryEject"/> 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

View File

@@ -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]

View File

@@ -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;
/// <summary>
/// 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.
/// </summary>
[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
}

View File

@@ -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";
/// <summary>
/// What size of cell fits into this component.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("slotSize")]
public PowerCellSize SlotSize { get; set; } = PowerCellSize.Small;
/// <summary>
/// The actual item-slot that contains the cell. Allows all the interaction logic to be handled by <see cref="ItemSlotsSystem"/>.
/// </summary>
/// <remarks>
/// Given that <see cref="PowerCellSystem"/> 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.
/// </remarks>
[DataField("cellSlot")]
public ItemSlot CellSlot = new();
/// <summary>
/// Name of the item-slot used to store cells. Determines the eject/insert verb text. E.g., "Eject > Power cell".
/// </summary>
/// <remarks>
/// This is simply used provide a default value for <see cref="CellSlot.Name"/>. 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").
/// </remarks>
[DataField("slotName")]
public readonly string SlotName = "power-cell-slot-component-slot-name-default"; // gets Loc.GetString()-ed by ItemSlotsSystem
/// <summary>
/// True if we don't want a cell inserted during map init. If a starting item is defined
/// in the <see cref="CellSlot"/> yaml definition, that always takes precedence.
/// </summary>
/// <remarks>
/// If false, the cell will start with a standard cell with a matching cell-size.
/// </remarks>
[DataField("startEmpty")]
public bool StartEmpty = false;
/// <summary>
/// Descriptive text to add to add when examining an entity with a cell slot. If empty or whitespace, will not add
/// any text.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("descFormatString")]
public string? DescFormatString { get; set; } = "power-cell-slot-component-description-default";
/// <summary>
/// Can this entity be inserted directly into a charging station? If false, you need to manually remove the power
/// cell and recharge it separately.
/// </summary>
[DataField("fitsInCharger")]
public bool FitsInCharger = true;
}
public class PowerCellChangedEvent : EntityEventArgs
{
public readonly bool Ejected;
public PowerCellChangedEvent(bool ejected)
{
Ejected = ejected;
}
}

View File

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

View File

@@ -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<PowerCellSlotComponent, ComponentInit>(OnCellSlotInit);
SubscribeLocalEvent<PowerCellSlotComponent, ComponentRemove>(OnCellSlotRemove);
SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnSlotExamined);
SubscribeLocalEvent<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellInserted);
SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellRemoved);
SubscribeLocalEvent<PowerCellSlotComponent, ContainerIsInsertingAttemptEvent>(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)));
}
}