using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Destructible; using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; namespace Content.IntegrationTests.Tests.Destructible { [TestFixture] [TestOf(typeof(DestructibleComponent))] [TestOf(typeof(Threshold))] public class DestructibleTests : ContentIntegrationTest { private static readonly string DestructibleEntityId = "DestructibleTestsDestructibleEntity"; private static readonly string Prototypes = $@" - type: entity id: {DestructibleEntityId} name: {DestructibleEntityId} components: - type: Damageable - type: Destructible thresholds: 20: triggersOnce: false 50: sound: /Audio/Effects/woodhit.ogg spawn: WoodPlank: min: 1 max: 1 acts: [""Breakage""] triggersOnce: false - type: TestThresholdListener "; private class TestThresholdListenerComponent : Component { public override string Name => "TestThresholdListener"; public List ThresholdsReached { get; } = new(); public override void HandleMessage(ComponentMessage message, IComponent component) { base.HandleMessage(message, component); switch (message) { case DestructibleThresholdReachedMessage msg: ThresholdsReached.Add(msg); break; } } } [Test] public async Task TestThresholdActivation() { var server = StartServerDummyTicker(new ServerContentIntegrationOption { ExtraPrototypes = Prototypes, ContentBeforeIoC = () => { IoCManager.Resolve().Register(); } }); await server.WaitIdleAsync(); var sEntityManager = server.ResolveDependency(); var sMapManager = server.ResolveDependency(); IEntity sDestructibleEntity = null; IDamageableComponent sDamageableComponent = null; DestructibleComponent sDestructibleComponent = null; TestThresholdListenerComponent sThresholdListenerComponent = null; await server.WaitPost(() => { var mapId = new MapId(1); var coordinates = new MapCoordinates(0, 0, mapId); sMapManager.CreateMap(mapId); sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleEntityId, coordinates); sDamageableComponent = sDestructibleEntity.GetComponent(); sDestructibleComponent = sDestructibleEntity.GetComponent(); sThresholdListenerComponent = sDestructibleEntity.GetComponent(); }); await server.WaitRunTicks(5); await server.WaitAssertion(() => { Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.Zero); }); await server.WaitAssertion(() => { Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true)); // No thresholds reached yet, the earliest one is at 20 damage Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.Zero); Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true)); // Only one threshold reached, 20 Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1)); var msg = sThresholdListenerComponent.ThresholdsReached[0]; // Check that it matches the total damage dealt Assert.That(msg.TotalDamage, Is.EqualTo(20)); var threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Acts, Is.EqualTo(0)); Assert.That(threshold.Sound, Is.Null.Or.Empty); Assert.That(threshold.Spawn, Is.Null); Assert.That(threshold.SoundCollection, Is.Null.Or.Empty); Assert.That(threshold.Triggered, Is.True); sThresholdListenerComponent.ThresholdsReached.Clear(); Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 30, true)); // Only one threshold reached, 50, since 20 was already reached before Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1)); msg = sThresholdListenerComponent.ThresholdsReached[0]; // Check that it matches the total damage dealt Assert.That(msg.TotalDamage, Is.EqualTo(50)); threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Acts, Is.EqualTo((int) ThresholdActs.Breakage)); Assert.That(threshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(threshold.Spawn, Is.Not.Null); Assert.That(threshold.Spawn.Count, Is.EqualTo(1)); Assert.That(threshold.Spawn.Single().Key, Is.EqualTo("WoodPlank")); Assert.That(threshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(threshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(threshold.SoundCollection, Is.Null.Or.Empty); Assert.That(threshold.Triggered, Is.True); sThresholdListenerComponent.ThresholdsReached.Clear(); // Damage for 50 again, up to 100 now Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true)); // No new thresholds reached as even though they don't only trigger once, the entity was not healed below the threshold Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); // Heal the entity for 40 damage, down to 60 sDamageableComponent.ChangeDamage(DamageType.Blunt, -40, true); // Thresholds don't work backwards Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); // Damage for 10, up to 70 sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true); // Not enough healing to de-trigger a threshold Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); // Heal by 30, down to 40 sDamageableComponent.ChangeDamage(DamageType.Blunt, -30, true); // Thresholds don't work backwards Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); // Damage up to 50 again sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true); // The 50 threshold should have triggered again, after being healed Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1)); msg = sThresholdListenerComponent.ThresholdsReached[0]; // Check that it matches the total damage dealt Assert.That(msg.TotalDamage, Is.EqualTo(50)); threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Acts, Is.EqualTo((int) ThresholdActs.Breakage)); Assert.That(threshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(threshold.Spawn, Is.Not.Null); Assert.That(threshold.Spawn.Count, Is.EqualTo(1)); Assert.That(threshold.Spawn.Single().Key, Is.EqualTo("WoodPlank")); Assert.That(threshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(threshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(threshold.SoundCollection, Is.Null.Or.Empty); Assert.That(threshold.Triggered, Is.True); // Reset thresholds reached sThresholdListenerComponent.ThresholdsReached.Clear(); // Heal all damage sDamageableComponent.Heal(); // Damage up to 50 sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true); // Check that the total damage matches Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50)); // Both thresholds should have triggered Assert.That(sThresholdListenerComponent.ThresholdsReached, Has.Exactly(2).Items); // Verify the first one, should be the lowest one (20) msg = sThresholdListenerComponent.ThresholdsReached[0]; Assert.That(msg.ThresholdAmount, Is.EqualTo(20)); // The total damage should be 50 Assert.That(msg.TotalDamage, Is.EqualTo(50)); threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Acts, Is.EqualTo(0)); Assert.That(threshold.Sound, Is.Null.Or.Empty); Assert.That(threshold.Spawn, Is.Null); Assert.That(threshold.SoundCollection, Is.Null.Or.Empty); Assert.That(threshold.Triggered, Is.True); // Verify the second one, should be the highest one (50) msg = sThresholdListenerComponent.ThresholdsReached[1]; Assert.That(msg.ThresholdAmount, Is.EqualTo(50)); // Check that it matches the total damage dealt Assert.That(msg.TotalDamage, Is.EqualTo(50)); threshold = msg.Threshold; // Check that it matches the YAML prototype Assert.That(threshold.Acts, Is.EqualTo((int) ThresholdActs.Breakage)); Assert.That(threshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg")); Assert.That(threshold.Spawn, Is.Not.Null); Assert.That(threshold.Spawn.Count, Is.EqualTo(1)); Assert.That(threshold.Spawn.Single().Key, Is.EqualTo("WoodPlank")); Assert.That(threshold.Spawn.Single().Value.Min, Is.EqualTo(1)); Assert.That(threshold.Spawn.Single().Value.Max, Is.EqualTo(1)); Assert.That(threshold.SoundCollection, Is.Null.Or.Empty); Assert.That(threshold.Triggered, Is.True); // Reset thresholds reached sThresholdListenerComponent.ThresholdsReached.Clear(); // Heal the entity completely sDamageableComponent.Heal(); // Check that the entity has 0 damage Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0)); // Set both thresholds to only trigger once foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values) { destructibleThreshold.TriggersOnce = true; } // Damage the entity up to 50 damage again sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true); // Check that the total damage matches Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50)); // No thresholds should have triggered as they were already triggered before, and they are set to only trigger once Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); // Set both thresholds to trigger multiple times foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values) { destructibleThreshold.TriggersOnce = false; } // Check that the total damage matches Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50)); // They shouldn't have been triggered by changing TriggersOnce Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty); }); } } }