#nullable enable using Content.Shared.Stacks; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; using static Robust.UnitTesting.RobustIntegrationTest; namespace Content.IntegrationTests.Tests.Interaction; public abstract partial class InteractionTest { /// /// Utility class for working with prototypes ids that may refer to stacks or entities. /// /// /// Intended to make tests easier by removing ambiguity around "SheetSteel1", "SheetSteel", and "Steel". All three /// should be treated identically by interaction tests. /// protected sealed class EntitySpecifier { /// /// Either the stack or entity prototype for this entity. Stack prototypes take priority. /// public string Prototype; /// /// The quantity. If the entity has a stack component, this is the total stack quantity. /// Otherwise this is the number of entities. /// /// /// If used for spawning and this number is larger than the max stack size, only a single stack will be spawned. /// public int Quantity; /// /// If true, a check has been performed to see if the prototype is an entity prototype with a stack component, /// in which case the specifier was converted into a stack-specifier /// public bool Converted; public EntitySpecifier(string prototype, int quantity, bool converted = false) { Assert.That(quantity, Is.GreaterThan(0)); Prototype = prototype; Quantity = quantity; Converted = converted; } public static implicit operator EntitySpecifier(string prototype) => new(prototype, 1); public static implicit operator EntitySpecifier((string, int) tuple) => new(tuple.Item1, tuple.Item2); /// /// Convert applicable entity prototypes into stack prototypes. /// public async Task ConvertToStack(IPrototypeManager protoMan, IComponentFactory factory, ServerIntegrationInstance server) { if (Converted) return; Converted = true; if (string.IsNullOrWhiteSpace(Prototype)) return; if (protoMan.HasIndex(Prototype)) return; if (!protoMan.TryIndex(Prototype, out var entProto)) { Assert.Fail($"Unknown prototype: {Prototype}"); return; } StackComponent? stack = null; await server.WaitPost(() => { entProto.TryGetComponent(factory.GetComponentName(), out stack); }); if (stack != null) Prototype = stack.StackTypeId; } } protected async Task SpawnEntity(EntitySpecifier spec, EntityCoordinates coords) { EntityUid uid = default!; if (ProtoMan.TryIndex(spec.Prototype, out var stackProto)) { await Server.WaitPost(() => { uid = SEntMan.SpawnEntity(stackProto.Spawn, coords); Stack.SetCount(uid, spec.Quantity); }); return uid; } if (!ProtoMan.TryIndex(spec.Prototype, out var entProto)) { Assert.Fail($"Unknown prototype: {spec.Prototype}"); return default; } StackComponent? stack = null; await Server.WaitPost(() => { entProto.TryGetComponent(Factory.GetComponentName(), out stack); }); if (stack != null) return await SpawnEntity((stack.StackTypeId, spec.Quantity), coords); Assert.That(spec.Quantity, Is.EqualTo(1), "SpawnEntity only supports returning a singular entity"); await Server.WaitPost(() => uid = SEntMan.SpawnAtPosition(spec.Prototype, coords)); return uid; } /// /// Convert an entity-uid to a matching entity specifier. Useful when doing entity lookups & checking that the /// right quantity of entities/materials were produced. Returns null if passed an entity with a null prototype. /// protected EntitySpecifier? ToEntitySpecifier(EntityUid uid) { if (SEntMan.TryGetComponent(uid, out StackComponent? stack)) return new EntitySpecifier(stack.StackTypeId, stack.Count) { Converted = true }; var meta = SEntMan.GetComponent(uid); if (meta.EntityPrototype is null) return null; return new(meta.EntityPrototype.ID, 1) { Converted = true }; } }