* Laws

* positronic brain and PAI rewrite

* MMI

* MMI pt. 2

* borg brain transfer

* Roleban support, Borg job (WIP), the end of mind shenaniganry

* battery drain, item slot cleanup, alerts

* visuals

* fix this pt1

* fix this pt2

* Modules, Lingering Stacks, Better borg flashlight

* Start on UI, fix battery alerts, expand activation/deactivation, low movement speed on no power.

* sprotes

* no zombie borgs

* oh fuck yeah i love a good relay

* charger

* fix the tiniest of sprite issues

* adjustable names

* a functional UI????

* foobar

* more modules

* this shit for some reason

* upstream

* genericize selectable borg modules

* upstream again

* holy fucking shit

* i love christ

* proper construction

* da job

* AA borgs

* and boom more shit

* admin logs

* laws redux

* ok just do this rq

* oh boy that looks like modules

* oh shit research

* testos passo

* so much shit holy fuck

* fuckit we SHIP

* last minute snags

* should've gotten me on a better day
This commit is contained in:
Nemanja
2023-08-12 17:39:58 -04:00
committed by GitHub
parent ac4f496535
commit 98fa00a21f
314 changed files with 7094 additions and 484 deletions

View File

@@ -1,5 +1,6 @@
using Content.Shared.Access.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
namespace Content.Shared.Access.Components
@@ -34,4 +35,16 @@ namespace Content.Shared.Access.Components
{
}
}
[ByRefEvent]
public record struct GetAccessTagsEvent(HashSet<string> Tags, IPrototypeManager PrototypeManager)
{
public void AddGroup(string group)
{
if (!PrototypeManager.TryIndex<AccessGroupPrototype>(group, out var groupPrototype))
return;
Tags.UnionWith(groupPrototype.Tags);
}
}
}

View File

@@ -11,11 +11,13 @@ using Robust.Shared.GameStates;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Prototypes;
namespace Content.Shared.Access.Systems;
public sealed class AccessReaderSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
@@ -272,23 +274,24 @@ public sealed class AccessReaderSystem : EntitySystem
/// Try to find <see cref="AccessComponent"/> on this item
/// or inside this item (if it's pda)
/// </summary>
private bool FindAccessTagsItem(EntityUid uid, [NotNullWhen(true)] out HashSet<string>? tags)
private bool FindAccessTagsItem(EntityUid uid, out HashSet<string> tags)
{
tags = new();
if (TryComp(uid, out AccessComponent? access))
{
tags = access.Tags;
return true;
tags.UnionWith(access.Tags);
}
if (TryComp(uid, out PdaComponent? pda) &&
pda.ContainedId is { Valid: true } id)
{
tags = EntityManager.GetComponent<AccessComponent>(id).Tags;
return true;
tags.UnionWith(EntityManager.GetComponent<AccessComponent>(id).Tags);
}
tags = null;
return false;
var ev = new GetAccessTagsEvent(tags, _prototype);
RaiseLocalEvent(uid, ref ev);
return tags.Count != 0;
}
/// <summary>

View File

@@ -15,5 +15,6 @@ public enum AlertCategory
Piloting,
Hunger,
Thirst,
Toxins
Toxins,
Battery
}

View File

@@ -22,6 +22,8 @@ namespace Content.Shared.Alert
HumanCrit,
HumanDead,
HumanHealth,
BorgBattery,
BorgBatteryNone,
PilotingShuttle,
Peckish,
Starving,

View File

@@ -0,0 +1,45 @@
using Robust.Shared.Containers;
namespace Content.Shared.Construction.Components;
/// <summary>
/// This is used for construction which requires a set of
/// entities with specific tags to be inserted into another entity.
/// todo: in a pr that isn't 6k loc, combine this with MechAssemblyComponent
/// </summary>
[RegisterComponent]
public sealed class PartAssemblyComponent : Component
{
/// <summary>
/// A dictionary of a set of parts to a list of tags for each assembly.
/// </summary>
[DataField("parts", required: true)]
public Dictionary<string, List<string>> Parts = new();
/// <summary>
/// The entry in <see cref="Parts"/> that is currently being worked on.
/// </summary>
[DataField("currentAssembly")]
public string? CurrentAssembly;
/// <summary>
/// The container where the parts are stored
/// </summary>
[DataField("containerId")]
public string ContainerId = "part-container";
/// <summary>
/// The container that stores all of the parts when
/// they're being assembled.
/// </summary>
[ViewVariables]
public Container PartsContainer = default!;
}
/// <summary>
/// Event raised when a valid part is inserted into the part assembly.
/// </summary>
public sealed class PartAssemblyPartInsertedEvent
{
}

View File

@@ -0,0 +1,147 @@
using Content.Shared.Construction.Components;
using Content.Shared.Interaction;
using Content.Shared.Tag;
using Robust.Shared.Containers;
namespace Content.Shared.Construction;
/// <summary>
/// This handles <see cref="PartAssemblyComponent"/>
/// </summary>
public sealed class PartAssemblySystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly TagSystem _tag = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<PartAssemblyComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<PartAssemblyComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<PartAssemblyComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
}
private void OnInit(EntityUid uid, PartAssemblyComponent component, ComponentInit args)
{
component.PartsContainer = _container.EnsureContainer<Container>(uid, component.ContainerId);
}
private void OnInteractUsing(EntityUid uid, PartAssemblyComponent component, InteractUsingEvent args)
{
if (!TryInsertPart(args.Used, uid, component))
return;
args.Handled = true;
}
private void OnEntRemoved(EntityUid uid, PartAssemblyComponent component, EntRemovedFromContainerMessage args)
{
if (args.Container.ID != component.ContainerId)
return;
if (component.PartsContainer.ContainedEntities.Count != 0)
return;
component.CurrentAssembly = null;
}
/// <summary>
/// Attempts to insert a part into the current assembly, starting one if there is none.
/// </summary>
public bool TryInsertPart(EntityUid part, EntityUid uid, PartAssemblyComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
string? assemblyId = null;
assemblyId ??= component.CurrentAssembly;
if (assemblyId == null)
{
foreach (var (id, tags) in component.Parts)
{
foreach (var tag in tags)
{
if (!_tag.HasTag(part, tag))
continue;
assemblyId = id;
break;
}
if (assemblyId != null)
break;
}
}
if (assemblyId == null)
return false;
if (!IsPartValid(uid, part, assemblyId, component))
return false;
component.CurrentAssembly = assemblyId;
component.PartsContainer.Insert(part);
var ev = new PartAssemblyPartInsertedEvent();
RaiseLocalEvent(uid, ev);
return true;
}
/// <summary>
/// Checks if the given entity is a valid item for the assembly.
/// </summary>
public bool IsPartValid(EntityUid uid, EntityUid part, string assemblyId, PartAssemblyComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return true;
if (!component.Parts.TryGetValue(assemblyId, out var tags))
return false;
var openTags = new List<string>(tags);
var contained = new List<EntityUid>(component.PartsContainer.ContainedEntities);
foreach (var tag in tags)
{
foreach (var ent in component.PartsContainer.ContainedEntities)
{
if (!contained.Contains(ent) || !_tag.HasTag(ent, tag))
continue;
openTags.Remove(tag);
contained.Remove(ent);
break;
}
}
foreach (var tag in openTags)
{
if (_tag.HasTag(part, tag))
return true;
}
return false;
}
public bool IsAssemblyFinished(EntityUid uid, string assemblyId, PartAssemblyComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return true;
if (!component.Parts.TryGetValue(assemblyId, out var parts))
return false;
var contained = new List<EntityUid>(component.PartsContainer.ContainedEntities);
foreach (var tag in parts)
{
var valid = false;
foreach (var ent in new List<EntityUid>(contained))
{
if (!_tag.HasTag(ent, tag))
continue;
valid = true;
contained.Remove(ent);
break;
}
if (!valid)
return false;
}
return true;
}
}

View File

@@ -41,6 +41,11 @@ namespace Content.Shared.Construction.Steps
return typeof(TemperatureConstructionGraphStep);
}
if (node.Has("assemblyId") || node.Has("guideString"))
{
return typeof(PartAssemblyConstructionGraphStep);
}
return null;
}

View File

@@ -0,0 +1,39 @@
using Content.Shared.Construction.Components;
using Content.Shared.Examine;
using JetBrains.Annotations;
namespace Content.Shared.Construction.Steps;
[DataDefinition]
public sealed class PartAssemblyConstructionGraphStep : ConstructionGraphStep
{
/// <summary>
/// A valid ID on <see cref="PartAssemblyComponent"/>'s dictionary of strings to part lists.
/// </summary>
[DataField("assemblyId")]
public string AssemblyId = string.Empty;
/// <summary>
/// A localization string used for
/// </summary>
[DataField("guideString")]
public string GuideString = "construction-guide-condition-part-assembly";
public bool Condition(EntityUid uid, IEntityManager entityManager)
{
return entityManager.System<PartAssemblySystem>().IsAssemblyFinished(uid, AssemblyId);
}
public override void DoExamine(ExaminedEvent args)
{
args.PushMarkup(Loc.GetString(GuideString));
}
public override ConstructionGuideEntry GenerateGuideEntry()
{
return new ConstructionGuideEntry
{
Localization = GuideString,
};
}
}

View File

@@ -233,4 +233,16 @@ namespace Content.Shared.Containers.ItemSlots
Priority = other.Priority;
}
}
/// <summary>
/// Event raised on the slot entity and the item being inserted to determine if an item can be inserted into an item slot.
/// </summary>
[ByRefEvent]
public record struct ItemSlotInsertAttemptEvent(EntityUid SlotEntity, EntityUid Item, EntityUid? User, ItemSlot Slot, bool Cancelled = false);
/// <summary>
/// Event raised on the slot entity and the item being inserted to determine if an item can be ejected from an item slot.
/// </summary>
[ByRefEvent]
public record struct ItemSlotEjectAttemptEvent(EntityUid SlotEntity, EntityUid Item, EntityUid? User, ItemSlot Slot, bool Cancelled = false);
}

View File

@@ -200,7 +200,7 @@ namespace Content.Shared.Containers.ItemSlots
if (!slot.InsertOnInteract)
continue;
if (!CanInsert(uid, args.Used, slot, swap: slot.Swap, popup: args.User))
if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User))
continue;
// Drop the held item onto the floor. Return if the user cannot drop.
@@ -244,7 +244,7 @@ namespace Content.Shared.Containers.ItemSlots
/// If a popup entity is given, and if the item slot is set to generate a popup message when it fails to
/// pass the whitelist, then this will generate a popup.
/// </remarks>
public bool CanInsert(EntityUid uid, EntityUid usedUid, ItemSlot slot, bool swap = false, EntityUid? popup = null)
public bool CanInsert(EntityUid uid, EntityUid usedUid, EntityUid? user, ItemSlot slot, bool swap = false, EntityUid? popup = null)
{
if (slot.Locked)
return false;
@@ -259,6 +259,12 @@ namespace Content.Shared.Containers.ItemSlots
return false;
}
var ev = new ItemSlotInsertAttemptEvent(uid, usedUid, user, slot);
RaiseLocalEvent(uid, ref ev);
RaiseLocalEvent(usedUid, ref ev);
if (ev.Cancelled)
return false;
return slot.ContainerSlot?.CanInsertIfEmpty(usedUid, EntityManager) ?? false;
}
@@ -283,7 +289,7 @@ namespace Content.Shared.Containers.ItemSlots
/// <returns>False if failed to insert item</returns>
public bool TryInsert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user)
{
if (!CanInsert(uid, item, slot))
if (!CanInsert(uid, item, user, slot))
return false;
Insert(uid, slot, item, user);
@@ -303,7 +309,7 @@ namespace Content.Shared.Containers.ItemSlots
if (hands.ActiveHand?.HeldEntity is not EntityUid held)
return false;
if (!CanInsert(uid, held, slot))
if (!CanInsert(uid, held, user, slot))
return false;
// hands.Drop(item) checks CanDrop action blocker
@@ -317,11 +323,17 @@ namespace Content.Shared.Containers.ItemSlots
#region Eject
public bool CanEject(ItemSlot slot)
public bool CanEject(EntityUid uid, EntityUid? user, ItemSlot slot)
{
if (slot.Locked || slot.Item == null)
return false;
var ev = new ItemSlotEjectAttemptEvent(uid, slot.Item.Value, user, slot);
RaiseLocalEvent(uid, ref ev);
RaiseLocalEvent(slot.Item.Value, ref ev);
if (ev.Cancelled)
return false;
return slot.ContainerSlot?.CanRemove(slot.Item.Value, EntityManager) ?? false;
}
@@ -352,7 +364,7 @@ namespace Content.Shared.Containers.ItemSlots
item = null;
// This handles logic with the slot itself
if (!CanEject(slot))
if (!CanEject(uid, user, slot))
return false;
item = slot.Item;
@@ -418,7 +430,7 @@ namespace Content.Shared.Containers.ItemSlots
foreach (var slot in itemSlots.Slots.Values)
{
// Disable slot insert if InsertOnInteract is true
if (slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, slot))
if (slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, args.User, slot))
continue;
var verbSubject = slot.Name != string.Empty
@@ -467,7 +479,7 @@ namespace Content.Shared.Containers.ItemSlots
// alt-click verb, there will be a "Take item" primary interaction verb.
continue;
if (!CanEject(slot))
if (!CanEject(uid, args.User, slot))
continue;
if (!_actionBlockerSystem.CanPickup(args.User, slot.Item!.Value))
@@ -506,7 +518,7 @@ namespace Content.Shared.Containers.ItemSlots
// If there are any slots that eject on left-click, add a "Take <item>" verb.
foreach (var slot in itemSlots.Slots.Values)
{
if (!slot.EjectOnInteract || !CanEject(slot))
if (!slot.EjectOnInteract || !CanEject(uid, args.User, slot))
continue;
if (!_actionBlockerSystem.CanPickup(args.User, slot.Item!.Value))
@@ -514,7 +526,7 @@ namespace Content.Shared.Containers.ItemSlots
var verbSubject = slot.Name != string.Empty
? Loc.GetString(slot.Name)
: EntityManager.GetComponent<MetaDataComponent>(slot.Item!.Value).EntityName ?? string.Empty;
: Name(slot.Item!.Value);
InteractionVerb takeVerb = new();
takeVerb.IconEntity = slot.Item;
@@ -535,7 +547,7 @@ namespace Content.Shared.Containers.ItemSlots
foreach (var slot in itemSlots.Slots.Values)
{
if (!slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, slot))
if (!slot.InsertOnInteract || !CanInsert(uid, args.Using.Value, args.User, slot))
continue;
var verbSubject = slot.Name != string.Empty

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Interaction.Components;
/// <summary>
/// This is used for entities which cannot move or interact in any way.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class BlockMovementComponent : Component
{
}

View File

@@ -0,0 +1,47 @@
using Content.Shared.Hands;
using Content.Shared.Interaction.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Movement.Events;
namespace Content.Shared.Interaction;
public partial class SharedInteractionSystem
{
public void InitializeBlocking()
{
SubscribeLocalEvent<BlockMovementComponent, UpdateCanMoveEvent>(OnMoveAttempt);
SubscribeLocalEvent<BlockMovementComponent, UseAttemptEvent>(CancelEvent);
SubscribeLocalEvent<BlockMovementComponent, InteractionAttemptEvent>(CancelEvent);
SubscribeLocalEvent<BlockMovementComponent, DropAttemptEvent>(CancelEvent);
SubscribeLocalEvent<BlockMovementComponent, PickupAttemptEvent>(CancelEvent);
SubscribeLocalEvent<BlockMovementComponent, ChangeDirectionAttemptEvent>(CancelEvent);
SubscribeLocalEvent<BlockMovementComponent, ComponentStartup>(OnBlockingStartup);
SubscribeLocalEvent<BlockMovementComponent, ComponentShutdown>(OnBlockingShutdown);
}
private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args)
{
if (component.LifeStage > ComponentLifeStage.Running)
return;
args.Cancel(); // no more scurrying around
}
private void CancelEvent(EntityUid uid, BlockMovementComponent component, CancellableEntityEventArgs args)
{
args.Cancel();
}
private void OnBlockingStartup(EntityUid uid, BlockMovementComponent component, ComponentStartup args)
{
_actionBlockerSystem.UpdateCanMove(uid);
}
private void OnBlockingShutdown(EntityUid uid, BlockMovementComponent component, ComponentShutdown args)
{
_actionBlockerSystem.UpdateCanMove(uid);
}
}

View File

@@ -100,6 +100,7 @@ namespace Content.Shared.Interaction
.Register<SharedInteractionSystem>();
InitializeRelay();
InitializeBlocking();
}
public override void Shutdown()

View File

@@ -40,6 +40,13 @@ namespace Content.Shared.Light
[DataField("toggleActionId", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
public string ToggleActionId = "ToggleLight";
/// <summary>
/// Whether or not the light can be toggled via standard interactions
/// (alt verbs, using in hand, etc)
/// </summary>
[DataField("toggleOnInteract")]
public bool ToggleOnInteract = true;
[DataField("toggleAction")]
public InstantAction? ToggleAction;

View File

@@ -118,6 +118,9 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
if (component.Blacklist != null && component.Blacklist.IsValid(item))
return false;
if (!_container.TryRemoveFromContainer(item))
return false;
if (user != null)
{
_adminLog.Add(LogType.Action, LogImpact.High,

View File

@@ -0,0 +1,17 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Mind;
[Serializable, NetSerializable]
public enum ToggleableGhostRoleVisuals : byte
{
Status
}
[Serializable, NetSerializable]
public enum ToggleableGhostRoleStatus : byte
{
Off,
Searching,
On
}

View File

@@ -0,0 +1,23 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.NameIdentifier;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class NameIdentifierComponent : Component
{
[DataField("group", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<NameIdentifierGroupPrototype>))]
public string Group = string.Empty;
/// <summary>
/// The randomly generated ID for this entity.
/// </summary>
[DataField("identifier"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public int Identifier = -1;
/// <summary>
/// The full name identifier for this entity.
/// </summary>
[DataField("fullIdentifier"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public string FullIdentifier = string.Empty;
}

View File

@@ -16,6 +16,13 @@ namespace Content.Shared.PAI
[RegisterComponent, NetworkedComponent]
public sealed class PAIComponent : Component
{
/// <summary>
/// The last person who activated this PAI.
/// Used for assigning the name.
/// </summary>
[ViewVariables]
public EntityUid? LastUser;
[DataField("midiAction", required: true, serverOnly: true)] // server only, as it uses a server-BUI event !type
public InstantAction? MidiAction;
}

View File

@@ -1,12 +1,9 @@
using Content.Shared.ActionBlocker;
using Content.Shared.Actions;
using Content.Shared.DragDrop;
using Content.Shared.Hands;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Movement;
using Content.Shared.Movement.Events;
using Robust.Shared.Serialization;
namespace Content.Shared.PAI
{
@@ -27,12 +24,6 @@ namespace Content.Shared.PAI
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PAIComponent, UseAttemptEvent>(OnUseAttempt);
SubscribeLocalEvent<PAIComponent, InteractionAttemptEvent>(OnInteractAttempt);
SubscribeLocalEvent<PAIComponent, DropAttemptEvent>(OnDropAttempt);
SubscribeLocalEvent<PAIComponent, PickupAttemptEvent>(OnPickupAttempt);
SubscribeLocalEvent<PAIComponent, UpdateCanMoveEvent>(OnMoveAttempt);
SubscribeLocalEvent<PAIComponent, ChangeDirectionAttemptEvent>(OnChangeDirectionAttempt);
SubscribeLocalEvent<PAIComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<PAIComponent, ComponentShutdown>(OnShutdown);
@@ -40,67 +31,15 @@ namespace Content.Shared.PAI
private void OnStartup(EntityUid uid, PAIComponent component, ComponentStartup args)
{
_blocker.UpdateCanMove(uid);
if (component.MidiAction != null)
_actionsSystem.AddAction(uid, component.MidiAction, null);
}
private void OnShutdown(EntityUid uid, PAIComponent component, ComponentShutdown args)
{
_blocker.UpdateCanMove(uid);
if (component.MidiAction != null)
_actionsSystem.RemoveAction(uid, component.MidiAction);
}
private void OnMoveAttempt(EntityUid uid, PAIComponent component, UpdateCanMoveEvent args)
{
if (component.LifeStage > ComponentLifeStage.Running)
return;
args.Cancel(); // no more scurrying around on lil robot legs.
}
private void OnChangeDirectionAttempt(EntityUid uid, PAIComponent component, ChangeDirectionAttemptEvent args)
{
// PAIs can't rotate, but decapitated heads and sentient crowbars can, life isn't fair. Seriously though, why
// tf does this have to be actively blocked, surely this should just not be blanket enabled for any player
// controlled entity. Same goes for moving really.
args.Cancel();
}
private void OnUseAttempt(EntityUid uid, PAIComponent component, UseAttemptEvent args)
{
args.Cancel();
}
private void OnInteractAttempt(EntityUid uid, PAIComponent component, InteractionAttemptEvent args)
{
args.Cancel();
}
private void OnDropAttempt(EntityUid uid, PAIComponent component, DropAttemptEvent args)
{
args.Cancel();
}
private void OnPickupAttempt(EntityUid uid, PAIComponent component, PickupAttemptEvent args)
{
args.Cancel();
}
}
[Serializable, NetSerializable]
public enum PAIVisuals : byte
{
Status
}
[Serializable, NetSerializable]
public enum PAIStatus : byte
{
Off,
Searching,
On
}
}

View File

@@ -0,0 +1,57 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Silicons.Borgs;
[Serializable, NetSerializable]
public enum BorgUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public sealed class BorgBuiState : BoundUserInterfaceState
{
public float ChargePercent;
public bool HasBattery;
public BorgBuiState(float chargePercent, bool hasBattery)
{
ChargePercent = chargePercent;
HasBattery = hasBattery;
}
}
[Serializable, NetSerializable]
public sealed class BorgEjectBrainBuiMessage : BoundUserInterfaceMessage
{
}
[Serializable, NetSerializable]
public sealed class BorgEjectBatteryBuiMessage : BoundUserInterfaceMessage
{
}
[Serializable, NetSerializable]
public sealed class BorgSetNameBuiMessage : BoundUserInterfaceMessage
{
public string Name;
public BorgSetNameBuiMessage(string name)
{
Name = name;
}
}
[Serializable, NetSerializable]
public sealed class BorgRemoveModuleBuiMessage : BoundUserInterfaceMessage
{
public EntityUid Module;
public BorgRemoveModuleBuiMessage(EntityUid module)
{
Module = module;
}
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.Borgs.Components;
/// <summary>
/// This is used for brains and mind receptacles
/// that can be inserted into a borg to transfer a mind.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
public sealed class BorgBrainComponent : Component
{
}

View File

@@ -0,0 +1,126 @@
using Content.Shared.Roles;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Silicons.Borgs.Components;
/// <summary>
/// This is used for the core body of a borg. This manages a borg's
/// "brain", legs, modules, and battery. Essentially the master component
/// for borg logic.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem)), AutoGenerateComponentState]
public sealed partial class BorgChassisComponent : Component
{
/// <summary>
/// Whether or not the borg currently has a player occupying it
/// </summary>
[DataField("hasPlayer")]
public bool HasPlayer;
/// <summary>
/// Whether or not the borg is activated, meaning it has access to modules and a heightened movement speed
/// </summary>
[DataField("activated"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public bool Activated;
#region Brain
/// <summary>
/// A whitelist for which entities count as valid brains
/// </summary>
[DataField("brainWhitelist")]
public EntityWhitelist? BrainWhitelist;
/// <summary>
/// The container ID for the brain
/// </summary>
[DataField("brainContainerId")]
public string BrainContainerId = "borg_brain";
[ViewVariables(VVAccess.ReadWrite)]
public ContainerSlot BrainContainer = default!;
public EntityUid? BrainEntity => BrainContainer.ContainedEntity;
/// <summary>
/// A brain entity that fills the <see cref="BrainContainer"/> on roundstart
/// </summary>
[DataField("startingBrain", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? StartingBrain;
#endregion
#region Modules
/// <summary>
/// A whitelist for what types of modules can be installed into this borg
/// </summary>
[DataField("moduleWhitelist")]
public EntityWhitelist? ModuleWhitelist;
/// <summary>
/// How many modules can be installed in this borg
/// </summary>
[DataField("maxModules"), ViewVariables(VVAccess.ReadWrite)]
public int MaxModules = 3;
/// <summary>
/// The ID for the module container
/// </summary>
[DataField("moduleContainerId")]
public string ModuleContainerId = "borg_module";
[ViewVariables(VVAccess.ReadWrite)]
public Container ModuleContainer = default!;
public int ModuleCount => ModuleContainer.ContainedEntities.Count;
/// <summary>
/// A list of modules that fill the borg on round start.
/// </summary>
[DataField("startingModules", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> StartingModules = new();
#endregion
/// <summary>
/// The job that corresponds to borgs
/// </summary>
[DataField("borgJobId", customTypeSerializer: typeof(PrototypeIdSerializer<JobPrototype>))]
public string BorgJobId = "Borg";
/// <summary>
/// The currently selected module
/// </summary>
[DataField("selectedModule")]
public EntityUid? SelectedModule;
/// <summary>
/// The access this cyborg has when a player is inhabiting it.
/// </summary>
[DataField("access"), ViewVariables(VVAccess.ReadWrite)]
[AutoNetworkedField]
public string AccessGroup = "AllAccess";
#region Visuals
[DataField("hasMindState")]
public string HasMindState = string.Empty;
[DataField("noMindState")]
public string NoMindState = string.Empty;
#endregion
}
[Serializable, NetSerializable]
public enum BorgVisuals : byte
{
HasPlayer
}
[Serializable, NetSerializable]
public enum BorgVisualLayers : byte
{
Light
}

View File

@@ -0,0 +1,33 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.Borgs.Components;
/// <summary>
/// This is used for modules that can be inserted into borgs
/// to give them unique abilities and attributes.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
public sealed class BorgModuleComponent : Component
{
/// <summary>
/// The entity this module is installed into
/// </summary>
[DataField("installedEntity")]
public EntityUid? InstalledEntity;
public bool Installed => InstalledEntity != null;
}
/// <summary>
/// Raised on a module when it is installed in order to add specific behavior to an entity.
/// </summary>
/// <param name="ChassisEnt"></param>
[ByRefEvent]
public readonly record struct BorgModuleInstalledEvent(EntityUid ChassisEnt);
/// <summary>
/// Raised on a module when it's uninstalled in order to
/// </summary>
/// <param name="ChassisEnt"></param>
[ByRefEvent]
public readonly record struct BorgModuleUninstalledEvent(EntityUid ChassisEnt);

View File

@@ -0,0 +1,51 @@
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Silicons.Borgs.Components;
/// <summary>
/// This is used for a <see cref="BorgModuleComponent"/> that provides items to the entity it's installed into.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
public sealed class ItemBorgModuleComponent : Component
{
/// <summary>
/// The items that are provided.
/// </summary>
[DataField("items", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>), required: true)]
public List<string> Items = new();
/// <summary>
/// The entities from <see cref="Items"/> that were spawned.
/// </summary>
[DataField("providedItems")]
public SortedDictionary<string, EntityUid> ProvidedItems = new();
/// <summary>
/// A counter that ensures a unique
/// </summary>
[DataField("handCounter")]
public int HandCounter;
/// <summary>
/// Whether or not the items have been created and stored in <see cref="ProvidedContainer"/>
/// </summary>
[DataField("itemsCrated")]
public bool ItemsCreated;
/// <summary>
/// A container where provided items are stored when not being used.
/// This is helpful as it means that items retain state.
/// </summary>
[ViewVariables]
public Container ProvidedContainer = default!;
/// <summary>
/// An ID for the container where provided items are stored when not used.
/// </summary>
[DataField("providedContainerId")]
public string ProvidedContainerId = "provided_container";
}

View File

@@ -0,0 +1,49 @@
using Content.Shared.Containers.ItemSlots;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Silicons.Borgs.Components;
/// <summary>
/// This is used for an entity that takes a brain
/// in an item slot before transferring consciousness.
/// Used for borg stuff.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
public sealed class MMIComponent : Component
{
/// <summary>
/// The ID of the itemslot that holds the brain.
/// </summary>
[DataField("brainSlotId")]
public string BrainSlotId = "brain_slot";
/// <summary>
/// The <see cref="ItemSlot"/> for this implanter
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public ItemSlot BrainSlot = default!;
[DataField("hasMindState")]
public string HasMindState = "mmi_alive";
[DataField("noMindState")]
public string NoMindState = "mmi_dead";
[DataField("noBrainState")]
public string NoBrainState = "mmi_off";
}
[Serializable, NetSerializable]
public enum MMIVisuals : byte
{
BrainPresent,
HasMind
}
[Serializable, NetSerializable]
public enum MMIVisualLayers : byte
{
Brain,
Base
}

View File

@@ -0,0 +1,17 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.Borgs.Components;
/// <summary>
/// This is used for an entity that is linked to an MMI.
/// Mostly for receiving events.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
public sealed class MMILinkedComponent : Component
{
/// <summary>
/// The MMI this entity is linked to.
/// </summary>
[DataField("linkedMMI")]
public EntityUid? LinkedMMI;
}

View File

@@ -0,0 +1,41 @@
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.GameStates;
namespace Content.Shared.Silicons.Borgs.Components;
/// <summary>
/// This is used for <see cref="BorgModuleComponent"/>s that can be "swapped" to, as opposed to having passive effects.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedBorgSystem))]
public sealed class SelectableBorgModuleComponent : Component
{
/// <summary>
/// The sidebar action for swapping to this module.
/// </summary>
[DataField("moduleSwapAction")]
public InstantAction ModuleSwapAction = new()
{
DisplayName = "action-name-swap-module",
Description = "action-desc-swap-module",
ItemIconStyle = ItemActionIconStyle.BigItem,
Event = new BorgModuleActionSelectedEvent(),
UseDelay = TimeSpan.FromSeconds(0.5f)
};
}
public sealed class BorgModuleActionSelectedEvent : InstantActionEvent
{
}
/// <summary>
/// Event raised by-ref on a module when it is selected
/// </summary>
[ByRefEvent]
public readonly record struct BorgModuleSelectedEvent(EntityUid Chassis);
/// <summary>
/// Event raised by-ref on a module when it is deselected.
/// </summary>
[ByRefEvent]
public readonly record struct BorgModuleUnselectedEvent(EntityUid Chassis);

View File

@@ -0,0 +1,38 @@
using Content.Shared.Damage;
using Content.Shared.Silicons.Borgs.Components;
namespace Content.Shared.Silicons.Borgs;
public abstract partial class SharedBorgSystem
{
public void InitializeRelay()
{
SubscribeLocalEvent<BorgChassisComponent, DamageModifyEvent>(RelayToModule);
}
protected void RelayToModule<T>(EntityUid uid, BorgChassisComponent component, T args) where T : class
{
var ev = new BorgModuleRelayedEvent<T>(args);
foreach (var module in component.ModuleContainer.ContainedEntities)
{
RaiseLocalEvent(module, ref ev);
}
}
protected void RelayRefToModule<T>(EntityUid uid, BorgChassisComponent component, ref T args) where T : class
{
var ev = new BorgModuleRelayedEvent<T>(args);
foreach (var module in component.ModuleContainer.ContainedEntities)
{
RaiseLocalEvent(module, ref ev);
}
}
}
[ByRefEvent]
public record struct BorgModuleRelayedEvent<TEvent>(TEvent Args)
{
public readonly TEvent Args = Args;
}

View File

@@ -0,0 +1,107 @@
using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
using Content.Shared.PowerCell.Components;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Wires;
using Robust.Shared.Containers;
namespace Content.Shared.Silicons.Borgs;
/// <summary>
/// This handles logic, interactions, and UI related to <see cref="BorgChassisComponent"/> and other related components.
/// </summary>
public abstract partial class SharedBorgSystem : EntitySystem
{
[Dependency] protected readonly SharedContainerSystem Container = default!;
[Dependency] protected readonly ItemSlotsSystem ItemSlots = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BorgChassisComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<BorgChassisComponent, ItemSlotInsertAttemptEvent>(OnItemSlotInsertAttempt);
SubscribeLocalEvent<BorgChassisComponent, ItemSlotEjectAttemptEvent>(OnItemSlotEjectAttempt);
SubscribeLocalEvent<BorgChassisComponent, EntInsertedIntoContainerMessage>(OnInserted);
SubscribeLocalEvent<BorgChassisComponent, EntRemovedFromContainerMessage>(OnRemoved);
SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
SubscribeLocalEvent<BorgChassisComponent, GetAccessTagsEvent>(OnGetAccessTags);
InitializeRelay();
}
private void OnItemSlotInsertAttempt(EntityUid uid, BorgChassisComponent component, ref ItemSlotInsertAttemptEvent args)
{
if (args.Cancelled)
return;
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlotComp) ||
!TryComp<WiresPanelComponent>(uid, out var panel))
return;
if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
return;
if (!panel.Open || args.User == uid)
args.Cancelled = true;
}
private void OnItemSlotEjectAttempt(EntityUid uid, BorgChassisComponent component, ref ItemSlotEjectAttemptEvent args)
{
if (args.Cancelled)
return;
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlotComp) ||
!TryComp<WiresPanelComponent>(uid, out var panel))
return;
if (!ItemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) || cellSlot != args.Slot)
return;
if (!panel.Open || args.User == uid)
args.Cancelled = true;
}
private void OnStartup(EntityUid uid, BorgChassisComponent component, ComponentStartup args)
{
var containerManager = EnsureComp<ContainerManagerComponent>(uid);
component.BrainContainer = Container.EnsureContainer<ContainerSlot>(uid, component.BrainContainerId, containerManager);
component.ModuleContainer = Container.EnsureContainer<Container>(uid, component.ModuleContainerId, containerManager);
}
protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
{
}
protected virtual void OnRemoved(EntityUid uid, BorgChassisComponent component, EntRemovedFromContainerMessage args)
{
}
private void OnRefreshMovementSpeedModifiers(EntityUid uid, BorgChassisComponent component, RefreshMovementSpeedModifiersEvent args)
{
if (component.Activated)
return;
if (!TryComp<MovementSpeedModifierComponent>(uid, out var movement))
return;
var sprintDif = movement.BaseWalkSpeed / movement.BaseSprintSpeed;
args.ModifySpeed(1f, sprintDif);
}
private void OnGetAccessTags(EntityUid uid, BorgChassisComponent component, ref GetAccessTagsEvent args)
{
if (!component.HasPlayer)
return;
args.AddGroup(component.AccessGroup);
}
}

View File

@@ -0,0 +1,14 @@
namespace Content.Shared.Silicons.Laws.Components;
/// <summary>
/// This is used for an entity that grants a special "obey" law when emagge.d
/// </summary>
[RegisterComponent]
public sealed class EmagSiliconLawComponent : Component
{
/// <summary>
/// The name of the person who emagged this law provider.
/// </summary>
[DataField("ownerName")]
public string? OwnerName;
}

View File

@@ -0,0 +1,57 @@
using Content.Shared.Actions;
using Content.Shared.Actions.ActionTypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Silicons.Laws.Components;
/// <summary>
/// This is used for entities which are bound to silicon laws and can view them.
/// </summary>
[RegisterComponent]
public sealed class SiliconLawBoundComponent : Component
{
/// <summary>
/// The sidebar action that toggles the laws screen.
/// </summary>
[DataField("viewLawsAction", customTypeSerializer: typeof(PrototypeIdSerializer<InstantActionPrototype>))]
public string ViewLawsAction = "ViewLaws";
/// <summary>
/// The action for toggling laws. Stored here so we can remove it later.
/// </summary>
[DataField("providedAction")]
public InstantAction? ProvidedAction;
}
[ByRefEvent]
public record struct GetSiliconLawsEvent(EntityUid Entity)
{
public EntityUid Entity = Entity;
public readonly List<SiliconLaw> Laws = new();
public bool Handled = false;
}
public sealed class ToggleLawsScreenEvent : InstantActionEvent
{
}
[NetSerializable, Serializable]
public enum SiliconLawsUiKey : byte
{
Key
}
[Serializable, NetSerializable]
public sealed class SiliconLawBuiState : BoundUserInterfaceState
{
public List<SiliconLaw> Laws;
public SiliconLawBuiState(List<SiliconLaw> laws)
{
Laws = laws;
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Silicons.Laws.Components;
/// <summary>
/// This is used for an entity which grants laws to a <see cref="SiliconLawBoundComponent"/>
/// </summary>
[RegisterComponent]
public sealed class SiliconLawProviderComponent : Component
{
/// <summary>
/// The laws that are provided.
/// </summary>
[DataField("laws", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<SiliconLawPrototype>))]
public List<string> Laws = new();
}

View File

@@ -0,0 +1,22 @@
using Content.Shared.Emag.Systems;
using Content.Shared.Silicons.Laws.Components;
namespace Content.Shared.Silicons.Laws;
/// <summary>
/// This handles getting and displaying the laws for silicons.
/// </summary>
public abstract class SharedSiliconLawSystem : EntitySystem
{
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<EmagSiliconLawComponent, GotEmaggedEvent>(OnGotEmagged);
}
protected virtual void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args)
{
component.OwnerName = Name(args.UserUid);
args.Handled = true;
}
}

View File

@@ -0,0 +1,55 @@
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Silicons.Laws;
[Virtual, DataDefinition]
[Serializable, NetSerializable]
public class SiliconLaw : IComparable<SiliconLaw>
{
/// <summary>
/// A locale string which is the actual text of the law.
/// </summary>
[DataField("lawString", required: true)]
public string LawString = string.Empty;
/// <summary>
/// The order of the law in the sequence.
/// Also is the identifier if <see cref="LawIdentifierOverride"/> is null.
/// </summary>
/// <remarks>
/// This is a fixedpoint2 only for the niche case of supporting laws that go between 0 and 1.
/// Funny.
/// </remarks>
[DataField("order", required: true)]
public FixedPoint2 Order;
/// <summary>
/// An identifier that overrides <see cref="Order"/> in the law menu UI.
/// </summary>
[DataField("lawIdentifierOverride")]
public string? LawIdentifierOverride;
public int CompareTo(SiliconLaw? other)
{
if (other == null)
return -1;
return Order.CompareTo(other.Order);
}
}
/// <summary>
/// This is a prototype for a law governing the behavior of silicons.
/// </summary>
[Prototype("siliconLaw")]
[Serializable, NetSerializable]
public sealed class SiliconLawPrototype : SiliconLaw, IPrototype
{
/// <inheritdoc/>
[IdDataField]
public string ID { get; } = default!;
}

View File

@@ -34,12 +34,19 @@ namespace Content.Shared.Stacks
[ViewVariables(VVAccess.ReadOnly)]
public bool Unlimited { get; set; }
/// <summary>
/// Lingering stacks will remain present even when there are no items.
/// Instead, they will become transparent.
/// </summary>
[DataField("lingering"), ViewVariables(VVAccess.ReadWrite)]
public bool Lingering;
[ViewVariables(VVAccess.ReadWrite)]
public bool ThrowIndividually { get; set; } = false;
[ViewVariables]
public bool UiUpdateNeeded { get; set; }
/// <summary>
/// Default IconLayer stack.
/// </summary>

View File

@@ -395,7 +395,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
var targetIsMob = HasComp<BodyComponent>(toInsert);
var storageIsItem = HasComp<ItemComponent>(container);
var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp<ItemComponent>(toInsert);
var allowedToEat = HasComp<ItemComponent>(toInsert);
// BEFORE REPLACING THIS WITH, I.E. A PROPERTY:
// Make absolutely 100% sure you have worked out how to stop people ending up in backpacks.
@@ -414,6 +414,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
}
}
if (allowedToEat && whitelist != null)
allowedToEat = whitelist.IsValid(toInsert);
return allowedToEat;
}

View File

@@ -66,7 +66,7 @@ namespace Content.Shared.Verbs
else if (_interactionSystem.InRangeUnobstructed(user, target))
{
// Note that being in a container does not count as an obstruction for InRangeUnobstructed
// Therefore, we need extra checks to ensure the item is actually accessible:
// Therefore, we need extra checks to ensure the item is actually accessible:
if (ContainerSystem.IsInSameOrParentContainer(user, target))
canAccess = true;
else
@@ -81,15 +81,23 @@ namespace Content.Shared.Verbs
EntityUid? @using = null;
if (TryComp(user, out HandsComponent? hands) && (force || _actionBlockerSystem.CanUseHeldEntity(user)))
{
@using = hands.ActiveHandEntity;
// Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
// This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
// their sprite.
if (TryComp(@using, out HandVirtualItemComponent? pull))
// if we don't actually have any hands, pass in a null value for the events.
if (hands.Count == 0)
{
@using = pull.BlockingEntity;
hands = null;
}
else
{
@using = hands.ActiveHandEntity;
// Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used".
// This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging
// their sprite.
if (TryComp(@using, out HandVirtualItemComponent? pull))
{
@using = pull.BlockingEntity;
}
}
}