Merge remote-tracking branch 'upstream/stable' into ed-30-04-2025-upstream-sync
# Conflicts: # Content.Client/Parallax/ParallaxControl.cs # Content.Client/UserInterface/Systems/Storage/Controls/ItemGridPiece.cs # Content.IntegrationTests/Tests/PostMapInitTest.cs # Content.Server/Chat/Managers/ChatManager.cs # Content.Server/Fluids/EntitySystems/PuddleSystem.Evaporation.cs # Content.Server/Labels/Label/LabelSystem.cs # Content.Shared/Actions/SharedActionsSystem.cs # Content.Shared/Fluids/Components/EvaporationComponent.cs # Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs # README.md # Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml # Resources/Prototypes/Maps/Pools/deathmatch.yml # Resources/Prototypes/Maps/arenas.yml
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.DeviceNetwork
|
||||
{
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.DeviceNetwork
|
||||
namespace Content.IntegrationTests.Tests.DeviceNetwork;
|
||||
|
||||
[Reflect(false)]
|
||||
public sealed class DeviceNetworkTestSystem : EntitySystem
|
||||
{
|
||||
[Reflect(false)]
|
||||
public sealed class DeviceNetworkTestSystem : EntitySystem
|
||||
public NetworkPayload LastPayload = default;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
public NetworkPayload LastPayload = default;
|
||||
base.Initialize();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DeviceNetworkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
}
|
||||
|
||||
SubscribeLocalEvent<DeviceNetworkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
}
|
||||
public void SendBaselineTestEvent(EntityUid uid)
|
||||
{
|
||||
RaiseLocalEvent(uid, new DeviceNetworkPacketEvent(0, "", 0, "", uid, new NetworkPayload()));
|
||||
}
|
||||
|
||||
public void SendBaselineTestEvent(EntityUid uid)
|
||||
{
|
||||
RaiseLocalEvent(uid, new DeviceNetworkPacketEvent(0, "", 0, "", uid, new NetworkPayload()));
|
||||
}
|
||||
|
||||
private void OnPacketReceived(EntityUid uid, DeviceNetworkComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
LastPayload = args.Data;
|
||||
}
|
||||
private void OnPacketReceived(EntityUid uid, DeviceNetworkComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
LastPayload = args.Data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#nullable enable annotations
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Disposal.Tube.Components;
|
||||
using Content.Server.Disposal.Unit.Components;
|
||||
using Content.Server.Disposal.Unit.EntitySystems;
|
||||
using Content.Server.Disposal.Unit;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using NUnit.Framework;
|
||||
using Content.Shared.Disposal.Tube;
|
||||
using Content.Shared.Disposal.Unit;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
@@ -30,7 +29,6 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
{
|
||||
var (_, toInsert, unit) = ev;
|
||||
var insertTransform = EntityManager.GetComponent<TransformComponent>(toInsert);
|
||||
var unitTransform = EntityManager.GetComponent<TransformComponent>(unit);
|
||||
// Not in a tube yet
|
||||
Assert.That(insertTransform.ParentUid, Is.EqualTo(unit));
|
||||
}, after: new[] { typeof(SharedDisposalUnitSystem) });
|
||||
@@ -163,6 +161,8 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var xformSystem = entityManager.System<SharedTransformSystem>();
|
||||
var disposalSystem = entityManager.System<DisposalUnitSystem>();
|
||||
var power = entityManager.System<PowerReceiverSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Spawn the entities
|
||||
@@ -191,7 +191,7 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
xformSystem.AnchorEntity(unitUid, entityManager.GetComponent<TransformComponent>(unitUid));
|
||||
|
||||
// No power
|
||||
Assert.That(unitComponent.Powered, Is.False);
|
||||
Assert.That(power.IsPowered(unitUid), Is.False);
|
||||
|
||||
// Can't insert the trunk or the unit into itself
|
||||
UnitInsertContains(unitUid, unitComponent, false, disposalSystem, disposalUnit, disposalTrunk);
|
||||
@@ -227,9 +227,9 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Remove power need
|
||||
Assert.That(entityManager.TryGetComponent(disposalUnit, out ApcPowerReceiverComponent power));
|
||||
power!.NeedsPower = false;
|
||||
unitComponent.Powered = true; //Power state changed event doesn't get fired smh
|
||||
Assert.That(entityManager.TryGetComponent(disposalUnit, out ApcPowerReceiverComponent powerComp));
|
||||
power.SetNeedsPower(disposalUnit, false);
|
||||
powerComp.Powered = true;
|
||||
|
||||
// Flush with a mob and an item
|
||||
Flush(disposalUnit, unitComponent, true, disposalSystem, human, wrench);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -9,6 +10,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
@@ -273,70 +275,103 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
// We consider only non-audio entities, as some entities will just play sounds when they spawn.
|
||||
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
|
||||
IEnumerable<EntityUid> Entities(IEntityManager entMan) => entMan.GetEntities().Where(entMan.HasComponent<AudioComponent>);
|
||||
|
||||
foreach (var protoId in protoIds)
|
||||
await Assert.MultipleAsync(async () =>
|
||||
{
|
||||
// TODO fix ninja
|
||||
// Currently ninja fails to equip their own loadout.
|
||||
if (protoId == "MobHumanSpaceNinja")
|
||||
continue;
|
||||
|
||||
var count = Count(server.EntMan);
|
||||
var clientCount = Count(client.EntMan);
|
||||
EntityUid uid = default;
|
||||
await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords));
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// If the entity deleted itself, check that it didn't spawn other entities
|
||||
if (!server.EntMan.EntityExists(uid))
|
||||
foreach (var protoId in protoIds)
|
||||
{
|
||||
if (Count(server.EntMan) != count)
|
||||
var count = Count(server.EntMan);
|
||||
var clientCount = Count(client.EntMan);
|
||||
var serverEntities = new HashSet<EntityUid>(Entities(server.EntMan));
|
||||
var clientEntities = new HashSet<EntityUid>(Entities(client.EntMan));
|
||||
EntityUid uid = default;
|
||||
await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords));
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// If the entity deleted itself, check that it didn't spawn other entities
|
||||
if (!server.EntMan.EntityExists(uid))
|
||||
{
|
||||
Assert.Fail($"Server prototype {protoId} failed on deleting itself");
|
||||
Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deleting itself\n" +
|
||||
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
|
||||
Assert.That(Count(client.EntMan), Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deleting itself\n" +
|
||||
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
|
||||
$"Server count was {count}.\n" +
|
||||
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Count(client.EntMan) != clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
|
||||
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
|
||||
$"Server was {count}.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Check that the number of entities has increased.
|
||||
Assert.That(Count(server.EntMan), Is.GreaterThan(count), $"Server prototype {protoId} failed on spawning as entity count didn't increase\n" +
|
||||
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
|
||||
Assert.That(Count(client.EntMan), Is.GreaterThan(clientCount), $"Client prototype {protoId} failed on spawning as entity count didn't increase\n" +
|
||||
$"Expected at least {clientCount} and found {client.EntMan.EntityCount}. " +
|
||||
$"Server count was {count}.\n" +
|
||||
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
|
||||
|
||||
// Check that the number of entities has increased.
|
||||
if (Count(server.EntMan) <= count)
|
||||
{
|
||||
Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
|
||||
}
|
||||
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
if (Count(client.EntMan) <= clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
|
||||
$"Expected at least {clientCount} and found {Count(client.EntMan)}. " +
|
||||
$"Server was {count}");
|
||||
// Check that the number of entities has gone back to the original value.
|
||||
Assert.That(Count(server.EntMan), Is.EqualTo(count), $"Server prototype {protoId} failed on deletion: count didn't reset properly\n" +
|
||||
BuildDiffString(serverEntities, Entities(server.EntMan), server.EntMan));
|
||||
Assert.That(client.EntMan.EntityCount, Is.EqualTo(clientCount), $"Client prototype {protoId} failed on deletion: count didn't reset properly:\n" +
|
||||
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
|
||||
$"Server count was {count}.\n" +
|
||||
BuildDiffString(clientEntities, Entities(client.EntMan), client.EntMan));
|
||||
}
|
||||
|
||||
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// Check that the number of entities has gone back to the original value.
|
||||
if (Count(server.EntMan) != count)
|
||||
{
|
||||
Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly");
|
||||
}
|
||||
|
||||
if (Count(client.EntMan) != clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
|
||||
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
|
||||
$"Server was {count}.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private static string BuildDiffString(IEnumerable<EntityUid> oldEnts, IEnumerable<EntityUid> newEnts, IEntityManager entMan)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var addedEnts = newEnts.Except(oldEnts);
|
||||
var removedEnts = oldEnts.Except(newEnts);
|
||||
if (addedEnts.Any())
|
||||
sb.AppendLine("Listing new entities:");
|
||||
foreach (var addedEnt in addedEnts)
|
||||
{
|
||||
sb.AppendLine(entMan.ToPrettyString(addedEnt));
|
||||
}
|
||||
if (removedEnts.Any())
|
||||
sb.AppendLine("Listing removed entities:");
|
||||
foreach (var removedEnt in removedEnts)
|
||||
{
|
||||
sb.AppendLine("\t" + entMan.ToPrettyString(removedEnt));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static bool HasRequiredDataField(Component component)
|
||||
{
|
||||
foreach (var field in component.GetType().GetFields())
|
||||
{
|
||||
foreach (var attribute in field.GetCustomAttributes(true))
|
||||
{
|
||||
if (attribute is not DataFieldAttribute dataField)
|
||||
continue;
|
||||
|
||||
if (dataField.Required)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
foreach (var property in component.GetType().GetProperties())
|
||||
{
|
||||
foreach (var attribute in property.GetCustomAttributes(true))
|
||||
{
|
||||
if (attribute is not DataFieldAttribute dataField)
|
||||
continue;
|
||||
|
||||
if (dataField.Required)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AllComponentsOneToOneDeleteTest()
|
||||
{
|
||||
@@ -363,9 +398,6 @@ namespace Content.IntegrationTests.Tests
|
||||
"ActivatableUI", // Requires enum key
|
||||
};
|
||||
|
||||
// TODO TESTS
|
||||
// auto ignore any components that have a "required" data field.
|
||||
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
@@ -383,9 +415,12 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
foreach (var type in componentFactory.AllRegisteredTypes)
|
||||
{
|
||||
var component = (Component) componentFactory.GetComponent(type);
|
||||
var component = (Component)componentFactory.GetComponent(type);
|
||||
var name = componentFactory.GetComponentName(type);
|
||||
|
||||
if (HasRequiredDataField(component))
|
||||
continue;
|
||||
|
||||
// If this component is ignored
|
||||
if (skipComponents.Contains(name))
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ public sealed class FailAndStartPresetTest
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
// Try to start nukeops without readying up
|
||||
await pair.WaitCommand("setgamepreset TestPresetTenPlayers");
|
||||
await pair.WaitCommand("setgamepreset TestPresetTenPlayers 9999");
|
||||
await pair.WaitCommand("startround");
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
@@ -99,7 +99,7 @@ public sealed class FailAndStartPresetTest
|
||||
// Ready up and start nukeops
|
||||
await pair.WaitClientCommand("toggleready True");
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay));
|
||||
await pair.WaitCommand("setgamepreset TestPreset");
|
||||
await pair.WaitCommand("setgamepreset TestPreset 9999");
|
||||
await pair.WaitCommand("startround");
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ public sealed class LocalizedDatasetPrototypeTest
|
||||
// Make sure the localization manager has a string for the LocId
|
||||
Assert.That(localizationMan.HasString(locId), $"LocalizedDataset {proto.ID} with prefix \"{proto.Values.Prefix}\" specifies {proto.Values.Count} entries, but no localized string was found matching {locId}!");
|
||||
}
|
||||
|
||||
// Check that count isn't set too low
|
||||
var nextId = proto.Values.Prefix + (proto.Values.Count + 1);
|
||||
Assert.That(localizationMan.HasString(nextId), Is.False, $"LocalizedDataset {proto.ID} with prefix \"{proto.Values.Prefix}\" specifies {proto.Values.Count} entries, but a localized string exists with ID {nextId}! Does count need to be raised?");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Construction.Completions;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.Destructible.Thresholds.Behaviors;
|
||||
using Content.Server.Lathe;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Construction.Steps;
|
||||
using Content.Shared.FixedPoint;
|
||||
@@ -14,10 +15,9 @@ using Content.Shared.Lathe;
|
||||
using Content.Shared.Materials;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
|
||||
@@ -28,6 +28,21 @@ namespace Content.IntegrationTests.Tests;
|
||||
[TestFixture]
|
||||
public sealed class MaterialArbitrageTest
|
||||
{
|
||||
// These recipes are currently broken and need fixing. You should not be adding to these sets.
|
||||
private readonly HashSet<string> _destructionArbitrageIgnore =
|
||||
[
|
||||
"BaseChemistryEmptyVial", "DrinkShotGlass", "SodiumLightTube", "DrinkGlassCoupeShaped",
|
||||
"LedLightBulb", "ExteriorLightTube", "LightTube", "DrinkGlass", "DimLightBulb", "LightBulb", "LedLightTube",
|
||||
"SheetRGlass1", "ChemistryEmptyBottle01", "WarmLightBulb",
|
||||
];
|
||||
|
||||
private readonly HashSet<string> _compositionArbitrageIgnore =
|
||||
[
|
||||
"FoodPlateSmall", "AirTank", "FoodPlateTin", "FoodPlateMuffinTin", "WeaponCapacitorRechargerCircuitboard",
|
||||
"WeaponCapacitorRechargerCircuitboard", "BorgChargerCircuitboard", "BorgChargerCircuitboard", "FoodPlate",
|
||||
"CellRechargerCircuitboard", "CellRechargerCircuitboard",
|
||||
];
|
||||
|
||||
[Test]
|
||||
public async Task NoMaterialArbitrage()
|
||||
{
|
||||
@@ -38,13 +53,12 @@ public sealed class MaterialArbitrageTest
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
var pricing = entManager.System<PricingSystem>();
|
||||
var stackSys = entManager.System<StackSystem>();
|
||||
var mapSystem = server.System<SharedMapSystem>();
|
||||
var latheSys = server.System<SharedLatheSystem>();
|
||||
var latheSys = server.System<LatheSystem>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
|
||||
Assert.That(mapSystem.IsInitialized(testMap.MapId));
|
||||
@@ -53,13 +67,24 @@ public sealed class MaterialArbitrageTest
|
||||
var compositionName = compFact.GetComponentName(typeof(PhysicalCompositionComponent));
|
||||
var materialName = compFact.GetComponentName(typeof(MaterialComponent));
|
||||
var destructibleName = compFact.GetComponentName(typeof(DestructibleComponent));
|
||||
var refinableName = compFact.GetComponentName(typeof(ToolRefinableComponent));
|
||||
|
||||
// get the inverted lathe recipe dictionary
|
||||
var latheRecipes = latheSys.InverseRecipes;
|
||||
|
||||
// Lets assume the possible lathe for resource multipliers:
|
||||
// TODO: each recipe can technically have its own cost multiplier associated with it, so this test needs redone to factor that in.
|
||||
var multiplier = MathF.Pow(0.85f, 3);
|
||||
// Find the lowest multiplier / optimal lathe that can be used to construct a recipie.
|
||||
var minMultiplier = new Dictionary<ProtoId<LatheRecipePrototype>, float>();
|
||||
|
||||
foreach (var (_, lathe) in pair.GetPrototypesWithComponent<LatheComponent>())
|
||||
{
|
||||
foreach (var recipe in latheSys.GetAllPossibleRecipes(lathe))
|
||||
{
|
||||
if (!minMultiplier.TryGetValue(recipe, out var min))
|
||||
min = 1;
|
||||
|
||||
minMultiplier[recipe] = Math.Min(min, lathe.MaterialUseMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
// create construction dictionary
|
||||
Dictionary<string, ConstructionComponent> constructionRecipes = new();
|
||||
@@ -122,6 +147,65 @@ public sealed class MaterialArbitrageTest
|
||||
|
||||
Dictionary<string, (Dictionary<string, int> Ents, Dictionary<string, int> Mats)> spawnedOnDestroy = new();
|
||||
|
||||
// cache the compositions of entities
|
||||
// If the entity is refineable (i.e. glass shared can be turned into glass, we take the greater of the two compositions.
|
||||
Dictionary<EntProtoId, Dictionary<string, int>> compositions = new();
|
||||
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
Dictionary<string, int>? baseComposition = null;
|
||||
|
||||
if (proto.Components.ContainsKey(materialName)
|
||||
&& proto.Components.TryGetValue(compositionName, out var compositionReg))
|
||||
{
|
||||
var compositionComp = (PhysicalCompositionComponent)compositionReg.Component;
|
||||
baseComposition = compositionComp.MaterialComposition;
|
||||
|
||||
}
|
||||
|
||||
if (!proto.Components.TryGetValue(refinableName, out var refinableReg))
|
||||
{
|
||||
if (baseComposition != null)
|
||||
compositions[proto.ID] = new(baseComposition);
|
||||
continue;
|
||||
}
|
||||
|
||||
var composition = new Dictionary<string, int>();
|
||||
compositions.Add(proto.ID, composition);
|
||||
|
||||
var refinable = (ToolRefinableComponent)refinableReg.Component;
|
||||
foreach (var refineResult in refinable.RefineResult)
|
||||
{
|
||||
if (refineResult.PrototypeId == null)
|
||||
continue;
|
||||
|
||||
var refineProto = protoManager.Index(refineResult.PrototypeId.Value);
|
||||
if (!refineProto.Components.ContainsKey(materialName))
|
||||
continue;
|
||||
|
||||
if (!refineProto.Components.TryGetValue(compositionName, out var refinedCompositionReg))
|
||||
continue;
|
||||
|
||||
var refinedCompositionComp = (PhysicalCompositionComponent)refinedCompositionReg.Component;
|
||||
|
||||
// This assumes refine results do not have complex spawn behaviours like exclusive groups.
|
||||
var quantity = refineResult.MaxAmount;
|
||||
|
||||
foreach (var (matId, amount) in refinedCompositionComp.MaterialComposition)
|
||||
{
|
||||
composition[matId] = quantity * amount + composition.GetValueOrDefault(matId);
|
||||
}
|
||||
}
|
||||
|
||||
if (baseComposition == null)
|
||||
continue;
|
||||
|
||||
// If the un-refined material quantity is greater than the refined quantity, we use that instead.
|
||||
foreach (var (matId, amount) in baseComposition)
|
||||
{
|
||||
composition[matId] = Math.Max(amount, composition.GetValueOrDefault(matId));
|
||||
}
|
||||
}
|
||||
|
||||
// Here we get the set of entities/materials spawned when destroying an entity.
|
||||
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
@@ -151,16 +235,10 @@ public sealed class MaterialArbitrageTest
|
||||
{
|
||||
spawnedEnts[key] = spawnedEnts.GetValueOrDefault(key) + value.Max;
|
||||
|
||||
var spawnProto = protoManager.Index<EntityPrototype>(key);
|
||||
|
||||
// get the amount of each material included in the entity
|
||||
|
||||
if (!spawnProto.Components.ContainsKey(materialName) ||
|
||||
!spawnProto.Components.TryGetValue(compositionName, out var compositionReg))
|
||||
if (!compositions.TryGetValue(key, out var composition))
|
||||
continue;
|
||||
|
||||
var mat = (PhysicalCompositionComponent) compositionReg.Component;
|
||||
foreach (var (matId, amount) in mat.MaterialComposition)
|
||||
foreach (var (matId, amount) in composition)
|
||||
{
|
||||
spawnedMats[matId] = value.Max * amount + spawnedMats.GetValueOrDefault(matId);
|
||||
}
|
||||
@@ -173,10 +251,13 @@ public sealed class MaterialArbitrageTest
|
||||
}
|
||||
|
||||
// This is the main loop where we actually check for destruction arbitrage
|
||||
Assert.Multiple(async () =>
|
||||
await Assert.MultipleAsync(async () =>
|
||||
{
|
||||
foreach (var (id, (spawnedEnts, spawnedMats)) in spawnedOnDestroy)
|
||||
{
|
||||
if (_destructionArbitrageIgnore.Contains(id))
|
||||
continue;
|
||||
|
||||
// Check cargo sell price
|
||||
// several constructible entities have no sell price
|
||||
// also this test only really matters if the entity is also purchaseable.... eh..
|
||||
@@ -190,6 +271,11 @@ public sealed class MaterialArbitrageTest
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
if (!minMultiplier.TryGetValue(recipe, out var multiplier))
|
||||
{
|
||||
server.Log.Info($"Unused lathe recipe? {recipe.ID}?");
|
||||
continue;
|
||||
}
|
||||
foreach (var (matId, amount) in recipe.Materials)
|
||||
{
|
||||
var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier);
|
||||
@@ -231,6 +317,9 @@ public sealed class MaterialArbitrageTest
|
||||
var edge = cur.GetEdge(node.Name);
|
||||
cur = node;
|
||||
|
||||
if (edge == null)
|
||||
continue;
|
||||
|
||||
foreach (var completion in edge.Completed)
|
||||
{
|
||||
if (completion is not SpawnPrototype spawnCompletion)
|
||||
@@ -253,9 +342,9 @@ public sealed class MaterialArbitrageTest
|
||||
}
|
||||
|
||||
// This is functionally the same loop as before, but now testing deconstruction rather than destruction.
|
||||
// This is pretty braindead. In principle construction graphs can have loops and whatnot.
|
||||
// This is pretty brain-dead. In principle construction graphs can have loops and whatnot.
|
||||
|
||||
Assert.Multiple(async () =>
|
||||
await Assert.MultipleAsync(async () =>
|
||||
{
|
||||
foreach (var (id, deconstructedMats) in deconstructionMaterials)
|
||||
{
|
||||
@@ -270,6 +359,11 @@ public sealed class MaterialArbitrageTest
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
if (!minMultiplier.TryGetValue(recipe, out var multiplier))
|
||||
{
|
||||
server.Log.Info($"Unused lathe recipe? {recipe.ID}?");
|
||||
continue;
|
||||
}
|
||||
foreach (var (matId, amount) in recipe.Materials)
|
||||
{
|
||||
var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier);
|
||||
@@ -291,7 +385,7 @@ public sealed class MaterialArbitrageTest
|
||||
}
|
||||
});
|
||||
|
||||
// create phyiscal composition dictionary
|
||||
// create physical composition dictionary
|
||||
// this doesn't account for the chemicals in the composition
|
||||
Dictionary<string, PhysicalCompositionComponent> physicalCompositions = new();
|
||||
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
|
||||
@@ -308,10 +402,13 @@ public sealed class MaterialArbitrageTest
|
||||
|
||||
// This is functionally the same loop as before, but now testing composition rather than destruction or deconstruction.
|
||||
// This doesn't take into account chemicals generated when deconstructing. Maybe it should.
|
||||
Assert.Multiple(async () =>
|
||||
await Assert.MultipleAsync(async () =>
|
||||
{
|
||||
foreach (var (id, compositionComponent) in physicalCompositions)
|
||||
{
|
||||
if (_compositionArbitrageIgnore.Contains(id))
|
||||
continue;
|
||||
|
||||
// Check cargo sell price
|
||||
var materialPrice = await GetDeconstructedPrice(compositionComponent.MaterialComposition);
|
||||
var chemicalPrice = await GetChemicalCompositionPrice(compositionComponent.ChemicalComposition);
|
||||
@@ -325,6 +422,11 @@ public sealed class MaterialArbitrageTest
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
if (!minMultiplier.TryGetValue(recipe, out var multiplier))
|
||||
{
|
||||
server.Log.Info($"Unused lathe recipe? {recipe.ID}?");
|
||||
continue;
|
||||
}
|
||||
foreach (var (matId, amount) in recipe.Materials)
|
||||
{
|
||||
var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier);
|
||||
|
||||
@@ -130,14 +130,14 @@ public sealed partial class MindTests
|
||||
ActorComponent actor = default!;
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(player, Is.EqualTo(mind.Session), "Player session does not match mind session");
|
||||
Assert.That(player.UserId, Is.EqualTo(mind.UserId), "Player UserId does not match mind UserId");
|
||||
Assert.That(entMan.System<MindSystem>().GetMind(player.UserId), Is.EqualTo(mindId));
|
||||
Assert.That(player.AttachedEntity, Is.EqualTo(mind.CurrentEntity), "Player is not attached to the mind's current entity.");
|
||||
Assert.That(entMan.EntityExists(mind.OwnedEntity), "The mind's current entity does not exist");
|
||||
Assert.That(mind.VisitingEntity == null || entMan.EntityExists(mind.VisitingEntity), "The minds visited entity does not exist.");
|
||||
Assert.That(entMan.TryGetComponent(mind.CurrentEntity, out actor));
|
||||
});
|
||||
Assert.That(actor.PlayerSession, Is.EqualTo(mind.Session));
|
||||
Assert.That(actor.PlayerSession.UserId, Is.EqualTo(mind.UserId));
|
||||
|
||||
return (mindId, mind);
|
||||
}
|
||||
@@ -161,7 +161,6 @@ public sealed partial class MindTests
|
||||
{
|
||||
Assert.That(player.Status, Is.EqualTo(SessionStatus.Disconnected));
|
||||
Assert.That(mind.UserId, Is.Not.Null);
|
||||
Assert.That(mind.Session, Is.Null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed partial class MindTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(entMan.Deleted(entity));
|
||||
Assert.That(mind.Comp.OwnedEntity, Is.Null);
|
||||
Assert.That(mind.Comp.OwnedEntity, Is.Not.EqualTo(entity));
|
||||
});
|
||||
|
||||
// Reconnect
|
||||
|
||||
@@ -49,10 +49,10 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
private static readonly string[] DoNotMapWhitelist =
|
||||
{
|
||||
//CrystallEdge Map replacement
|
||||
//CrystallEdge Maps
|
||||
"/Maps/_CP14/dev_map.yml",
|
||||
"/Maps/_CP14/Dungeon/artifact_room.yml",
|
||||
//CrystallEdge Map replacement end
|
||||
//CrystallEdge Maps end
|
||||
"/Maps/centcomm.yml",
|
||||
"/Maps/bagel.yml", // Contains mime's rubber stamp --> Either fix this, remove the category, or remove this comment if intentional.
|
||||
"/Maps/gate.yml", // Contains positronic brain and LSE-1200c "Perforator"
|
||||
|
||||
@@ -81,7 +81,7 @@ public sealed class StorageInteractionTest : InteractionTest
|
||||
{
|
||||
var uid = ToClient(target);
|
||||
var hotbar = GetWidget<HotbarGui>();
|
||||
var storageContainer = GetControlFromField<Control>(nameof(HotbarGui.StorageContainer), hotbar);
|
||||
var storageContainer = GetControlFromField<Control>(nameof(HotbarGui.SingleStorageContainer), hotbar);
|
||||
return GetControlFromChildren<ItemGridPiece>(c => c.Entity == uid, storageContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public sealed class TileConstructionTests : InteractionTest
|
||||
AssertGridCount(1);
|
||||
|
||||
// Lattice -> Plating
|
||||
await InteractUsing(Steel);
|
||||
await InteractUsing(FloorItem);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
await AssertTile(Plating);
|
||||
AssertGridCount(1);
|
||||
|
||||
419
Content.IntegrationTests/Tests/XenoArtifactTest.cs
Normal file
419
Content.IntegrationTests/Tests/XenoArtifactTest.cs
Normal file
@@ -0,0 +1,419 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Xenoarchaeology.Artifact;
|
||||
using Content.Shared.Xenoarchaeology.Artifact.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class XenoArtifactTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: TestArtifact
|
||||
parent: BaseXenoArtifact
|
||||
name: artifact
|
||||
components:
|
||||
- type: XenoArtifact
|
||||
isGenerationRequired: false
|
||||
effectsTable: !type:NestedSelector
|
||||
tableId: XenoArtifactEffectsDefaultTable
|
||||
|
||||
- type: entity
|
||||
id: TestGenArtifactFlat
|
||||
parent: BaseXenoArtifact
|
||||
name: artifact
|
||||
components:
|
||||
- type: XenoArtifact
|
||||
isGenerationRequired: true
|
||||
nodeCount:
|
||||
min: 2
|
||||
max: 2
|
||||
segmentSize:
|
||||
min: 1
|
||||
max: 1
|
||||
nodesPerSegmentLayer:
|
||||
min: 1
|
||||
max: 1
|
||||
effectsTable: !type:NestedSelector
|
||||
tableId: XenoArtifactEffectsDefaultTable
|
||||
|
||||
- type: entity
|
||||
id: TestGenArtifactTall
|
||||
parent: BaseXenoArtifact
|
||||
name: artifact
|
||||
components:
|
||||
- type: XenoArtifact
|
||||
isGenerationRequired: true
|
||||
nodeCount:
|
||||
min: 2
|
||||
max: 2
|
||||
segmentSize:
|
||||
min: 2
|
||||
max: 2
|
||||
nodesPerSegmentLayer:
|
||||
min: 1
|
||||
max: 1
|
||||
effectsTable: !type:NestedSelector
|
||||
tableId: XenoArtifactEffectsDefaultTable
|
||||
|
||||
- type: entity
|
||||
id: TestGenArtifactFull
|
||||
name: artifact
|
||||
components:
|
||||
- type: XenoArtifact
|
||||
isGenerationRequired: true
|
||||
nodeCount:
|
||||
min: 6
|
||||
max: 6
|
||||
segmentSize:
|
||||
min: 6
|
||||
max: 6
|
||||
nodesPerSegmentLayer:
|
||||
min: 2
|
||||
max: 2
|
||||
effectsTable: !type:NestedSelector
|
||||
tableId: XenoArtifactEffectsDefaultTable
|
||||
|
||||
- type: entity
|
||||
id: TestArtifactNode
|
||||
name: artifact node
|
||||
components:
|
||||
- type: XenoArtifactNode
|
||||
maxDurability: 3
|
||||
";
|
||||
|
||||
/// <summary>
|
||||
/// Checks that adding nodes and edges properly adds them into the adjacency matrix
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task XenoArtifactAddNodeTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var artifactUid = entManager.Spawn("TestArtifact");
|
||||
var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
|
||||
|
||||
// Create 3 nodes
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
|
||||
|
||||
Assert.That(artifactSystem.GetAllNodeIndices(artifactEnt).Count(), Is.EqualTo(3));
|
||||
|
||||
// Add connection from 1 -> 2 and 2-> 3
|
||||
artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
|
||||
|
||||
// Assert that successors and direct successors are counted correctly for node 1.
|
||||
Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node1!.Value).Count, Is.EqualTo(1));
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1!.Value).Count, Is.EqualTo(2));
|
||||
// Assert that we didn't somehow get predecessors on node 1.
|
||||
Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node1!.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node1!.Value), Is.Empty);
|
||||
|
||||
// Assert that successors and direct successors are counted correctly for node 2.
|
||||
Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
|
||||
// Assert that predecessors and direct predecessors are counted correctly for node 2.
|
||||
Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node2!.Value), Has.Count.EqualTo(1));
|
||||
|
||||
// Assert that successors and direct successors are counted correctly for node 3.
|
||||
Assert.That(artifactSystem.GetDirectSuccessorNodes(artifactEnt, node3!.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node3!.Value), Is.Empty);
|
||||
// Assert that predecessors and direct predecessors are counted correctly for node 3.
|
||||
Assert.That(artifactSystem.GetDirectPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(1));
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3!.Value), Has.Count.EqualTo(2));
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to make sure that removing nodes properly cleans up all connections.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task XenoArtifactRemoveNodeTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var artifactUid = entManager.Spawn("TestArtifact");
|
||||
var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
|
||||
|
||||
// Create 3 nodes
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node5, false));
|
||||
|
||||
Assert.That(artifactSystem.GetAllNodeIndices(artifactEnt).Count(), Is.EqualTo(5));
|
||||
|
||||
// Add connection: 1 -> 2 -> 3 -> 4 -> 5
|
||||
artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node3!.Value, node4!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node4!.Value, node5!.Value, false);
|
||||
|
||||
// Make sure we have a continuous connection between the two ends of the graph.
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Has.Count.EqualTo(4));
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node5.Value), Has.Count.EqualTo(4));
|
||||
|
||||
// Remove the node and make sure it's no longer in the artifact.
|
||||
Assert.That(artifactSystem.RemoveNode(artifactEnt, node3!.Value, false));
|
||||
Assert.That(artifactSystem.TryGetIndex(artifactEnt, node3!.Value, out _), Is.False, "Node 3 still present in artifact.");
|
||||
|
||||
// Check to make sure that we got rid of all the connections.
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node2!.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up series of linked nodes and ensures that resizing the adjacency matrix doesn't disturb the connections
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task XenoArtifactResizeTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var artifactUid = entManager.Spawn("TestArtifact");
|
||||
var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
|
||||
|
||||
// Create 3 nodes
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
|
||||
|
||||
// Add connection: 1 -> 2 -> 3
|
||||
artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
|
||||
|
||||
// Make sure our connection is set up
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node1.Value), Is.False);
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node2.Value), Is.False);
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node3.Value), Is.False);
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node1.Value), Is.False);
|
||||
|
||||
Assert.That(artifactSystem.GetIndex(artifactEnt, node1!.Value), Is.EqualTo(0));
|
||||
Assert.That(artifactSystem.GetIndex(artifactEnt, node2!.Value), Is.EqualTo(1));
|
||||
Assert.That(artifactSystem.GetIndex(artifactEnt, node3!.Value), Is.EqualTo(2));
|
||||
|
||||
// Add a new node, resizing the original adjacency matrix and array.
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4));
|
||||
|
||||
// Check that our connections haven't changed.
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node1.Value), Is.False);
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node2.Value), Is.False);
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node3.Value), Is.False);
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node3.Value, node1.Value), Is.False);
|
||||
|
||||
// Has our array shifted any when we resized?
|
||||
Assert.That(artifactSystem.GetIndex(artifactEnt, node1!.Value), Is.EqualTo(0));
|
||||
Assert.That(artifactSystem.GetIndex(artifactEnt, node2!.Value), Is.EqualTo(1));
|
||||
Assert.That(artifactSystem.GetIndex(artifactEnt, node3!.Value), Is.EqualTo(2));
|
||||
|
||||
// Check that 4 didn't somehow end up with connections
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node4!.Value), Is.Empty);
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if removing a node and adding a new node into its place in the adjacency matrix doesn't accidentally retain extra data.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task XenoArtifactReplaceTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var artifactUid = entManager.Spawn("TestArtifact");
|
||||
var artifactEnt = (artifactUid, comp: entManager.GetComponent<XenoArtifactComponent>(artifactUid));
|
||||
|
||||
// Create 3 nodes
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
|
||||
|
||||
// Add connection: 1 -> 2 -> 3
|
||||
artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node2!.Value, node3!.Value, false);
|
||||
|
||||
// Make sure our connection is set up
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node1.Value, node2.Value));
|
||||
Assert.That(artifactSystem.NodeHasEdge(artifactEnt, node2.Value, node3.Value));
|
||||
|
||||
// Remove middle node, severing connections
|
||||
artifactSystem.RemoveNode(artifactEnt, node2!.Value, false);
|
||||
|
||||
// Make sure our connection are properly severed.
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3.Value), Is.Empty);
|
||||
|
||||
// Make sure our matrix is 3x3
|
||||
Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixRows, Is.EqualTo(3));
|
||||
Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixColumns, Is.EqualTo(3));
|
||||
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
|
||||
|
||||
// Make sure that adding in a new node didn't add a new slot but instead re-used the middle slot.
|
||||
Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixRows, Is.EqualTo(3));
|
||||
Assert.That(artifactEnt.Item2.NodeAdjacencyMatrixColumns, Is.EqualTo(3));
|
||||
|
||||
// Ensure that all connections are still severed
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node1.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node3.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetSuccessorNodes(artifactEnt, node4!.Value), Is.Empty);
|
||||
Assert.That(artifactSystem.GetPredecessorNodes(artifactEnt, node4!.Value), Is.Empty);
|
||||
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the active nodes are properly detected.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task XenoArtifactBuildActiveNodesTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var artifactUid = entManager.Spawn("TestArtifact");
|
||||
Entity<XenoArtifactComponent> artifactEnt = (artifactUid, entManager.GetComponent<XenoArtifactComponent>(artifactUid));
|
||||
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node1, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node2, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node3, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node4, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node5, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node6, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node7, false));
|
||||
Assert.That(artifactSystem.AddNode(artifactEnt, "TestArtifactNode", out var node8, false));
|
||||
|
||||
// /----( 6 )
|
||||
// /----[*3 ]-/----( 7 )----( 8 )
|
||||
// /
|
||||
// / /----[*5 ]
|
||||
// [ 1 ]--/----[ 2 ]--/----( 4 )
|
||||
// Diagram of the example generation. Nodes in [brackets] are unlocked, nodes in (braces) are locked
|
||||
// and nodes with an *asterisk are supposed to be active.
|
||||
artifactSystem.AddEdge(artifactEnt, node1!.Value, node2!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node1!.Value, node3!.Value, false);
|
||||
|
||||
artifactSystem.AddEdge(artifactEnt, node2!.Value, node4!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node2!.Value, node5!.Value, false);
|
||||
|
||||
artifactSystem.AddEdge(artifactEnt, node3!.Value, node6!.Value, false);
|
||||
artifactSystem.AddEdge(artifactEnt, node3!.Value, node7!.Value, false);
|
||||
|
||||
artifactSystem.AddEdge(artifactEnt, node7!.Value, node8!.Value, false);
|
||||
|
||||
artifactSystem.SetNodeUnlocked(node1!.Value);
|
||||
artifactSystem.SetNodeUnlocked(node2!.Value);
|
||||
artifactSystem.SetNodeUnlocked(node3!.Value);
|
||||
artifactSystem.SetNodeUnlocked(node5!.Value);
|
||||
|
||||
NetEntity[] expectedActiveNodes =
|
||||
[
|
||||
entManager.GetNetEntity(node3!.Value.Owner),
|
||||
entManager.GetNetEntity(node5!.Value.Owner)
|
||||
];
|
||||
Assert.That(artifactEnt.Comp.CachedActiveNodes, Is.SupersetOf(expectedActiveNodes));
|
||||
Assert.That(artifactEnt.Comp.CachedActiveNodes, Has.Count.EqualTo(expectedActiveNodes.Length));
|
||||
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task XenoArtifactGenerateSegmentsTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var artifactSystem = entManager.System<SharedXenoArtifactSystem>();
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var artifact1Uid = entManager.Spawn("TestGenArtifactFlat");
|
||||
Entity<XenoArtifactComponent> artifact1Ent = (artifact1Uid, entManager.GetComponent<XenoArtifactComponent>(artifact1Uid));
|
||||
|
||||
var segments1 = artifactSystem.GetSegments(artifact1Ent);
|
||||
Assert.That(segments1.Count, Is.EqualTo(2));
|
||||
Assert.That(segments1[0].Count, Is.EqualTo(1));
|
||||
Assert.That(segments1[1].Count, Is.EqualTo(1));
|
||||
|
||||
var artifact2Uid = entManager.Spawn("TestGenArtifactTall");
|
||||
Entity<XenoArtifactComponent> artifact2Ent = (artifact2Uid, entManager.GetComponent<XenoArtifactComponent>(artifact2Uid));
|
||||
|
||||
var segments2 = artifactSystem.GetSegments(artifact2Ent);
|
||||
Assert.That(segments2.Count, Is.EqualTo(1));
|
||||
Assert.That(segments2[0].Count, Is.EqualTo(2));
|
||||
|
||||
var artifact3Uid = entManager.Spawn("TestGenArtifactFull");
|
||||
Entity<XenoArtifactComponent> artifact3Ent = (artifact3Uid, entManager.GetComponent<XenoArtifactComponent>(artifact3Uid));
|
||||
|
||||
var segments3 = artifactSystem.GetSegments(artifact3Ent);
|
||||
Assert.That(segments3.Count, Is.EqualTo(1));
|
||||
Assert.That(segments3.Sum(x => x.Count), Is.EqualTo(6));
|
||||
var nodesDepths = segments3[0].Select(x => x.Comp.Depth).ToArray();
|
||||
Assert.That(nodesDepths.Distinct().Count(), Is.EqualTo(3));
|
||||
var grouped = nodesDepths.ToLookup(x => x);
|
||||
Assert.That(grouped[0].Count(), Is.EqualTo(2));
|
||||
Assert.That(grouped[1].Count(), Is.GreaterThanOrEqualTo(2)); // tree is attempting sometimes to get wider (so it will look like a tree)
|
||||
Assert.That(grouped[2].Count(), Is.LessThanOrEqualTo(2)); // maintain same width or, if we used 3 nodes on previous layer - we only have 1 left!
|
||||
|
||||
});
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user