Move board spawning out of DoorSystem (#11772)

This commit is contained in:
Leon Friedrich
2022-10-17 05:43:33 +13:00
committed by GitHub
parent ad3615dcc6
commit bb61eb7f54
13 changed files with 235 additions and 56 deletions

View File

@@ -1,4 +1,5 @@
using Content.Server.Construction.Components; using Content.Server.Construction.Components;
using Content.Server.Containers;
using Content.Shared.Construction; using Content.Shared.Construction;
using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Prototypes;
using Content.Shared.Construction.Steps; using Content.Shared.Construction.Steps;
@@ -281,20 +282,33 @@ namespace Content.Server.Construction
Resolve(uid, ref containerManager, false); Resolve(uid, ref containerManager, false);
// We create the new entity. // We create the new entity.
var newUid = EntityManager.SpawnEntity(newEntity, transform.Coordinates); var newUid = EntityManager.CreateEntityUninitialized(newEntity, transform.Coordinates);
// Construction transferring. // Construction transferring.
var newConstruction = EntityManager.EnsureComponent<ConstructionComponent>(newUid); var newConstruction = EntityManager.EnsureComponent<ConstructionComponent>(newUid);
// We set the graph and node accordingly... Then we append our containers to theirs. // Transfer all construction-owned containers.
newConstruction.Containers.UnionWith(construction.Containers);
// Prevent MapInitEvent spawned entities from spawning into the containers.
// Containers created by ChangeNode() actions do not exist until after this function is complete,
// but this should be fine, as long as the target entity properly declared its managed containers.
if (TryComp(newUid, out ContainerFillComponent? containerFill) && containerFill.IgnoreConstructionSpawn)
{
foreach (var id in newConstruction.Containers)
{
containerFill.Containers.Remove(id);
}
}
EntityManager.InitializeAndStartEntity(newUid);
// We set the graph and node accordingly.
ChangeGraph(newUid, userUid, construction.Graph, construction.Node, false, newConstruction); ChangeGraph(newUid, userUid, construction.Graph, construction.Node, false, newConstruction);
if (construction.TargetNode is {} targetNode) if (construction.TargetNode is {} targetNode)
SetPathfindingTarget(newUid, targetNode, newConstruction); SetPathfindingTarget(newUid, targetNode, newConstruction);
// Transfer all construction-owned containers.
newConstruction.Containers.UnionWith(construction.Containers);
// Transfer all pending interaction events too. // Transfer all pending interaction events too.
while (construction.InteractionQueue.TryDequeue(out var ev)) while (construction.InteractionQueue.TryDequeue(out var ev))
{ {

View File

@@ -0,0 +1,95 @@
using Content.Server.Storage.Components;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Content.Server.Containers;
/// <summary>
/// Component for spawning entity prototypes into containers on map init.
/// </summary>
/// <remarks>
/// Unlike <see cref="StorageFillComponent"/> this is deterministic and supports arbitrary containers. While this
/// could maybe be merged with that component, it would require significant changes to <see
/// cref="EntitySpawnCollection.GetSpawns"/>, which is also used by several other systems.
/// </remarks>
[RegisterComponent]
public sealed class ContainerFillComponent : Component
{
[DataField("containers", customTypeSerializer:typeof(ContainerFillSerializer))]
public readonly Dictionary<string, List<string>> Containers = new();
/// <summary>
/// If true, entities spawned via the construction system will not have entities spawned into containers managed
/// by the construction system.
/// </summary>
[DataField("ignoreConstructionSpawn")]
public bool IgnoreConstructionSpawn = true;
}
// all of this exists just to validate prototype ids.
// it would be nice if you could specify only a type validator and not have to re-implement everything else.
// or a dictionary serializer that accepts a custom type serializer for the dictionary values
public sealed class ContainerFillSerializer : ITypeSerializer<Dictionary<string, List<string>>, MappingDataNode>
{
private static PrototypeIdListSerializer<EntityPrototype> ListSerializer => new();
public ValidationNode Validate(
ISerializationManager serializationManager,
MappingDataNode node,
IDependencyCollection dependencies,
ISerializationContext? context = null)
{
var mapping = new Dictionary<ValidationNode, ValidationNode>();
foreach (var (key, val) in node.Children)
{
var keyVal = serializationManager.ValidateNode<string>(key, context);
var listVal = (val is SequenceDataNode seq)
? ListSerializer.Validate(serializationManager, seq, dependencies, context)
: new ErrorNode(val, "ContainerFillComponent prototypes must be a sequence/list");
mapping.Add(keyVal, listVal);
}
return new ValidatedMappingNode(mapping);
}
public Dictionary<string, List<string>> Copy(
ISerializationManager serializationManager,
Dictionary<string, List<string>> source,
Dictionary<string, List<string>> target,
bool skipHook,
ISerializationContext? context = null)
{
serializationManager.Copy(source, ref target, context, skipHook);
return target;
}
public Dictionary<string, List<string>> Read(
ISerializationManager serializationManager,
MappingDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null,
Dictionary<string, List<string>>? value = null)
{
return serializationManager.Read(node, context, skipHook, value);
}
public DataNode Write(ISerializationManager serializationManager,
Dictionary<string, List<string>> value,
IDependencyCollection dependencies,
bool alwaysWrite = false,
ISerializationContext? context = null)
{
return serializationManager.WriteValue(value, alwaysWrite, context);
}
}

View File

@@ -0,0 +1,47 @@
using Robust.Shared.Containers;
using Robust.Shared.Map;
namespace Content.Server.Containers;
public sealed class ContainerFillSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ContainerFillComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, ContainerFillComponent component, MapInitEvent args)
{
if (!TryComp(uid, out ContainerManagerComponent? containerComp))
{
Logger.Error($"Entity {ToPrettyString(uid)} with a {nameof(ContainerFillComponent)} has no {nameof(ContainerManagerComponent)}.");
return;
}
var xform = Transform(uid);
var coords = new EntityCoordinates(uid, Vector2.Zero);
foreach (var (contaienrId, prototypes) in component.Containers)
{
if (!_containerSystem.TryGetContainer(uid, contaienrId, out var container, containerComp))
{
Logger.Error($"Entity {ToPrettyString(uid)} with a {nameof(ContainerFillComponent)} is missing a container ({contaienrId}).");
continue;
}
foreach (var proto in prototypes)
{
var ent = Spawn(proto, coords);
if (!container.Insert(ent, EntityManager, null, xform))
{
Logger.Error($"Entity {ToPrettyString(uid)} with a {nameof(ContainerFillComponent)} failed to insert an entity: {ToPrettyString(ent)}.");
Transform(ent).AttachToGridOrMap();
break;
}
}
}
}
}

View File

@@ -18,7 +18,6 @@ using Content.Shared.Tools.Components;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Player; using Robust.Shared.Player;
using System.Linq; using System.Linq;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
@@ -38,7 +37,6 @@ public sealed class DoorSystem : SharedDoorSystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<DoorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DoorComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ConstructionSystem) }); SubscribeLocalEvent<DoorComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ConstructionSystem) });
// Mob prying doors // Mob prying doors
@@ -274,30 +272,6 @@ public sealed class DoorSystem : SharedDoorSystem
TryOpen(uid, door, otherUid); TryOpen(uid, door, otherUid);
} }
private void OnMapInit(EntityUid uid, DoorComponent door, MapInitEvent args)
{
// Ensure that the construction component is aware of the board container.
if (TryComp(uid, out ConstructionComponent? construction))
_constructionSystem.AddContainer(uid, "board", construction);
// We don't do anything if this is null or empty.
if (string.IsNullOrEmpty(door.BoardPrototype))
return;
var container = _containerSystem.EnsureContainer<Container>(uid, "board", out var existed);
if (existed && container.ContainedEntities.Count != 0)
{
// We already contain a board. Note: We don't check if it's the right one!
return;
}
var board = EntityManager.SpawnEntity(door.BoardPrototype, Transform(uid).Coordinates);
if(!container.Insert(board))
Logger.Warning($"Couldn't insert board {ToPrettyString(board)} into door {ToPrettyString(uid)}!");
}
private void OnEmagged(EntityUid uid, DoorComponent door, GotEmaggedEvent args) private void OnEmagged(EntityUid uid, DoorComponent door, GotEmaggedEvent args)
{ {
if(TryComp<AirlockComponent>(uid, out var airlockComponent)) if(TryComp<AirlockComponent>(uid, out var airlockComponent))

View File

@@ -169,9 +169,6 @@ public sealed class DoorComponent : Component, ISerializationHooks
} }
#endregion #endregion
[DataField("board", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? BoardPrototype;
[DataField("pryingQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))] [DataField("pryingQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
public string PryingQuality = "Prying"; public string PryingQuality = "Prying";

View File

@@ -41,8 +41,10 @@
- FullTileMask - FullTileMask
layer: layer:
- AirlockLayer - AirlockLayer
- type: ContainerFill
containers:
board: [ DoorElectronics ]
- type: Door - type: Door
board: DoorElectronics
crushDamage: crushDamage:
types: types:
Blunt: 15 Blunt: 15
@@ -96,6 +98,8 @@
- type: Construction - type: Construction
graph: Airlock graph: Airlock
node: airlock node: airlock
containers:
- board
- type: IconSmooth - type: IconSmooth
key: walls key: walls
mode: NoSprite mode: NoSprite

View File

@@ -35,8 +35,13 @@
- FullTileMask - FullTileMask
layer: layer:
- WallLayer - WallLayer
- type: ContainerFill
containers:
board: [ DoorElectronics ]
- type: ContainerContainer
containers:
board: !type:Container
- type: Door - type: Door
board: DoorElectronics
crushDamage: crushDamage:
types: types:
Blunt: 50 Blunt: 50

View File

@@ -32,7 +32,6 @@
bumpOpen: false bumpOpen: false
closeTimeTwo: 0.4 closeTimeTwo: 0.4
openTimeTwo: 0.4 openTimeTwo: 0.4
board: DoorElectronics
crushDamage: crushDamage:
types: types:
Blunt: 15 Blunt: 15

View File

@@ -1,5 +1,6 @@
- type: entity - type: entity
id: Firelock id: BaseFirelock
abstract: true
parent: BaseStructure parent: BaseStructure
name: firelock name: firelock
description: Apply crowbar. description: Apply crowbar.
@@ -97,14 +98,27 @@
enabled: false enabled: false
- type: Occluder - type: Occluder
enabled: false enabled: false
- type: Construction
graph: Firelock
node: Firelock
- type: WallMount - type: WallMount
arc: 360 arc: 360
- type: StaticPrice - type: StaticPrice
price: 150 price: 150
- type: entity
id: Firelock
parent: BaseFirelock
components:
- type: ContainerContainer
containers:
board: !type:Container
- type: Construction
graph: Firelock
node: Firelock
containers:
- board
- type: ContainerFill
containers:
board: [ FirelockElectronics ]
- type: entity - type: entity
id: FirelockGlass id: FirelockGlass
parent: Firelock parent: Firelock
@@ -126,10 +140,12 @@
- GlassAirlockLayer - GlassAirlockLayer
- type: Door - type: Door
occludes: false occludes: false
- type: Construction
node: FirelockGlass
- type: entity - type: entity
id: FirelockEdge id: FirelockEdge
parent: Firelock parent: BaseFirelock
name: firelock name: firelock
components: components:
- type: Sprite - type: Sprite

View File

@@ -4,6 +4,8 @@
name: shutter name: shutter
abstract: true abstract: true
description: One shudders to think about what might be behind this shutter. description: One shudders to think about what might be behind this shutter.
placement:
mode: SnapgridCenter
components: components:
- type: Sprite - type: Sprite
netsync: false netsync: false
@@ -22,8 +24,13 @@
- FullTileMask - FullTileMask
layer: layer:
- AirlockLayer - AirlockLayer
- type: ContainerFill
containers:
board: [ DoorElectronics ]
- type: ContainerContainer
containers:
board: !type:Container
- type: Door - type: Door
board: DoorElectronics
bumpOpen: false bumpOpen: false
clickOpen: false clickOpen: false
closeTimeOne: 0.2 closeTimeOne: 0.2
@@ -78,8 +85,6 @@
messagePerceivedByOthers: comp-window-knock messagePerceivedByOthers: comp-window-knock
interactSuccessSound: interactSuccessSound:
path: /Audio/Effects/glass_knock.ogg path: /Audio/Effects/glass_knock.ogg
placement:
mode: SnapgridCenter
- type: entity - type: entity
id: ShuttersNormal id: ShuttersNormal
@@ -89,6 +94,8 @@
- type: Construction - type: Construction
graph: Shutters graph: Shutters
node: Shutters node: Shutters
containers:
- board
- type: entity - type: entity
id: ShuttersNormalOpen id: ShuttersNormalOpen
@@ -105,9 +112,6 @@
airBlocked: false airBlocked: false
- type: RadiationBlocker - type: RadiationBlocker
enabled: false enabled: false
- type: Construction
graph: Shutters
node: Shutters
- type: entity - type: entity
id: ShuttersRadiation id: ShuttersRadiation
@@ -125,6 +129,8 @@
- type: Construction - type: Construction
graph: Shutters graph: Shutters
node: ShuttersRadiation node: ShuttersRadiation
containers:
- board
- type: entity - type: entity
id: ShuttersRadiationOpen id: ShuttersRadiationOpen
@@ -133,9 +139,6 @@
components: components:
- type: Door - type: Door
state: Open state: Open
- type: Construction
graph: Shutters
node: ShuttersRadiation
- type: Occluder - type: Occluder
enabled: false enabled: false
- type: Physics - type: Physics
@@ -160,6 +163,8 @@
- type: Construction - type: Construction
graph: Shutters graph: Shutters
node: ShuttersWindow node: ShuttersWindow
containers:
- board
- type: entity - type: entity
id: ShuttersWindowOpen id: ShuttersWindowOpen
@@ -168,9 +173,6 @@
components: components:
- type: Door - type: Door
state: Open state: Open
- type: Construction
graph: Shutters
node: ShuttersWindow
- type: Physics - type: Physics
canCollide: false canCollide: false
- type: Airtight - type: Airtight
@@ -192,6 +194,8 @@
- type: Construction - type: Construction
graph: Shutters graph: Shutters
node: frame1 node: frame1
containers:
- board
- type: InteractionOutline - type: InteractionOutline
- type: Damageable - type: Damageable
damageContainer: Inorganic damageContainer: Inorganic

View File

@@ -76,9 +76,14 @@
openPanelVisible: true openPanelVisible: true
# needed so that windoors will close regardless of whether there are people in it; it doesn't crush after all # needed so that windoors will close regardless of whether there are people in it; it doesn't crush after all
safety: false safety: false
- type: ContainerFill
containers:
board: [ DoorElectronics ]
- type: ContainerContainer
containers:
board: !type:Container
- type: Door - type: Door
canCrush: false canCrush: false
board: DoorElectronics
openSound: openSound:
path: /Audio/Machines/windoor_open.ogg path: /Audio/Machines/windoor_open.ogg
closeSound: closeSound:
@@ -111,6 +116,8 @@
- type: Construction - type: Construction
graph: Windoor graph: Windoor
node: windoor node: windoor
containers:
- board
- type: StaticPrice - type: StaticPrice
price: 100 price: 100

View File

@@ -148,7 +148,7 @@
prototype: SheetGlass1 prototype: SheetGlass1
amount: 2 amount: 2
steps: steps:
- tool: Screwing - tool: Prying
doAfter: 1 doAfter: 1

View File

@@ -379,6 +379,23 @@
conditions: conditions:
- !type:TileNotBlocked - !type:TileNotBlocked
- type: construction
name: glass firelock
id: FirelockGlass
graph: Firelock
startNode: start
targetNode: FirelockGlass
category: construction-category-structures
description: This is a firelock - it locks an area when a fire alarm in the area is triggered. Don't get squished!
icon:
sprite: Structures/Doors/Airlocks/Glass/firelock.rsi
state: closed
objectType: Structure
placementMode: SnapgridCenter
canBuildInImpassable: false
conditions:
- !type:TileNotBlocked
- type: construction - type: construction
name: shutter name: shutter
id: Shutters id: Shutters