Add Modular grenades (chemnades). (#7138)

This commit is contained in:
Leon Friedrich
2022-03-25 17:17:29 +13:00
committed by GitHub
parent 414c03978d
commit 1b0e7ae0f5
51 changed files with 994 additions and 96 deletions

View File

@@ -0,0 +1,33 @@
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.Serialization;
namespace Content.Shared.Payload.Components;
/// <summary>
/// Chemical payload that mixes the solutions of two drain-able solution containers when triggered.
/// </summary>
[RegisterComponent]
public sealed class ChemicalPayloadComponent : Component
{
[DataField("beakerSlotA", required: true)]
public ItemSlot BeakerSlotA = new();
[DataField("beakerSlotB", required: true)]
public ItemSlot BeakerSlotB = new();
}
[Serializable, NetSerializable]
public enum ChemicalPayloadVisuals : byte
{
Slots
}
[Flags]
[Serializable, NetSerializable]
public enum ChemicalPayloadFilledSlots : byte
{
None = 0,
Left = 1 << 0,
Right = 1 << 1,
Both = Left | Right,
}

View File

@@ -0,0 +1,12 @@
namespace Content.Shared.Payload.Components;
/// <summary>
/// Component that enables payloads and payload triggers to function.
/// </summary>
/// <remarks>
/// If an entity with a <see cref="PayloadTriggerComponent"/> is installed into a an entity with a <see
/// cref="PayloadCaseComponent"/>, the trigger will grant components to the case-entity. If the case entity is
/// triggered, it will forward the trigger onto any contained payload entity.
/// </remarks>
[RegisterComponent]
public sealed class PayloadCaseComponent : Component { }

View File

@@ -0,0 +1,45 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Payload.Components;
/// <summary>
/// Component for providing the means of triggering an explosive payload. Used in grenade construction.
/// </summary>
/// <remarks>
/// This component performs two functions. Firstly, it will add or remove other components to some entity when this
/// item is installed inside of it. This is intended for use with constructible grenades. For example, this allows
/// you to add things like <see cref="OnUseTimerTriggerComponent"/>, or <see cref="TriggerOnProximityComponent"/>.
/// This is required because otherwise you would have to forward arbitrary interaction directed at the casing
/// through to the trigger, which would be quite complicated. Also proximity triggers don't really work inside of
/// containers.
///
/// Secondly, if the entity that this component is attached to is ever triggered directly (e.g., via a device
/// network message), the trigger will be forwarded to the device that this entity is installed in (if any).
/// </remarks>
[RegisterComponent, NetworkedComponent]
public sealed class PayloadTriggerComponent : Component
{
/// <summary>
/// If true, triggering this entity will also cause the parent of this entity to be triggered.
/// </summary>
public bool Active = false;
/// <summary>
/// List of components to add or remove from an entity when this trigger is (un)installed.
/// </summary>
[DataField("components", serverOnly:true, readOnly: true)]
public readonly EntityPrototype.ComponentRegistry? Components = null;
/// <summary>
/// Keeps track of what components this trigger has granted to the payload case.
/// </summary>
/// <remarks>
/// This is required in case someone creates a construction graph that accepts more than one trigger, and those
/// trigger grant the same type of component (or the case just innately has that component). This list is used
/// when removing the component, to ensure that removal of this trigger only removes the components that it was
/// responsible for adding.
/// </remarks>
[DataField("grantedComponents", serverOnly: true)]
public readonly HashSet<Type> GrantedComponents = new();
}

View File

@@ -0,0 +1,53 @@
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Payload.Components;
using Robust.Shared.Containers;
namespace Content.Shared.Payload.EntitySystems;
public sealed class ChemicalPayloadSystem : EntitySystem
{
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChemicalPayloadComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<ChemicalPayloadComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<ChemicalPayloadComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
SubscribeLocalEvent<ChemicalPayloadComponent, EntRemovedFromContainerMessage>(OnContainerModified);
}
private void OnContainerModified(EntityUid uid, ChemicalPayloadComponent component, ContainerModifiedMessage args)
{
UpdateAppearance(uid, component);
}
private void UpdateAppearance(EntityUid uid, ChemicalPayloadComponent? component = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref component, ref appearance, false))
return;
var filled = ChemicalPayloadFilledSlots.None;
if (component.BeakerSlotA.HasItem)
filled |= ChemicalPayloadFilledSlots.Left;
if (component.BeakerSlotB.HasItem)
filled |= ChemicalPayloadFilledSlots.Right;
appearance.SetData(ChemicalPayloadVisuals.Slots, filled);
}
private void OnComponentInit(EntityUid uid, ChemicalPayloadComponent payload, ComponentInit args)
{
_itemSlotsSystem.AddItemSlot(uid, "BeakerSlotA", payload.BeakerSlotA);
_itemSlotsSystem.AddItemSlot(uid, "BeakerSlotB", payload.BeakerSlotB);
}
private void OnComponentRemove(EntityUid uid, ChemicalPayloadComponent payload, ComponentRemove args)
{
_itemSlotsSystem.RemoveItemSlot(uid, payload.BeakerSlotA);
_itemSlotsSystem.RemoveItemSlot(uid, payload.BeakerSlotB);
}
}