Add destructible component threshold triggers (#3080)
* WIP changes * Fix tests, merge conflict and trigger once behavior * Update yml * Change test strings to be consts * Fix total damage types and classes triggers * Simplify damage trigger logic, move state to Threshold * Update outdated code and docs * Change the name of IBehavior back to IThresholdBehavior * Change human gibbing to trigger at 400 brute damage * Change gibbing from brute to blunt damage * Fix one (1) typo * Add damage class trigger test * Add missing nullable enable to thresholds
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(TotalDamageClassesTrigger))]
|
||||
public class DestructibleDamageClassTest : ContentIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
IEntity sDestructibleEntity;
|
||||
IDamageableComponent sDamageableComponent = 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(DestructibleDamageClassEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Raise brute damage to 5
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 5, true));
|
||||
|
||||
// No thresholds reached yet, the earliest one is at 10 damage
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise brute damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 5, true));
|
||||
|
||||
// No threshold reached, burn needs to be 10 as well
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
|
||||
|
||||
// One threshold reached, brute 10 + burn 10
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold brute 10 + burn 10
|
||||
var msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
var threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Behaviors, Is.Empty);
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
Assert.IsInstanceOf<TotalDamageClassesTrigger>(threshold.Trigger);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Raise brute damage to 20
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 20
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Lower brute damage to 0
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -20, true));
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise brute damage back up to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
|
||||
|
||||
// 10 brute + 10 burn threshold reached, brute was healed and brought back to its threshold amount and slash stayed the same
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Heal both classes of damage to 0
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -10, true));
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, -20, true));
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise brute damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
|
||||
|
||||
// Both classes of damage were healed and then raised again, the threshold should have been reached as triggers once is default false
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold brute 10 + burn 10
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Behaviors, Is.Empty);
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
Assert.IsInstanceOf<TotalDamageClassesTrigger>(threshold.Trigger);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Change triggers once to true
|
||||
threshold.TriggersOnce = true;
|
||||
|
||||
// Heal brute and burn back to 0
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -10, true));
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, -10, true));
|
||||
|
||||
// No new thresholds reached from healing
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise brute damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
|
||||
|
||||
// No new thresholds reached as triggers once is set to true and it already triggered before
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(TotalDamageTypesTrigger))]
|
||||
public class DestructibleDamageTypeTest : ContentIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
IEntity sDestructibleEntity;
|
||||
IDamageableComponent sDamageableComponent = 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(DestructibleDamageTypeEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Raise blunt damage to 5
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 5, true));
|
||||
|
||||
// No thresholds reached yet, the earliest one is at 10 damage
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 5, true));
|
||||
|
||||
// No threshold reached, slash needs to be 10 as well
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
|
||||
|
||||
// One threshold reached, blunt 10 + slash 10
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold blunt 10 + slash 10
|
||||
var msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
var threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Behaviors, Is.Empty);
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
Assert.IsInstanceOf<TotalDamageTypesTrigger>(threshold.Trigger);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Raise blunt damage to 20
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 20
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Lower blunt damage to 0
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -20, true));
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage back up to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
|
||||
|
||||
// 10 blunt + 10 slash threshold reached, blunt was healed and brought back to its threshold amount and slash stayed the same
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Heal both types of damage to 0
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -10, true));
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, -20, true));
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
|
||||
|
||||
// Both types of damage were healed and then raised again, the threshold should have been reached as triggers once is default false
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold blunt 10 + slash 10
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Behaviors, Is.Empty);
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
Assert.IsInstanceOf<TotalDamageTypesTrigger>(threshold.Trigger);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Change triggers once to true
|
||||
threshold.TriggersOnce = true;
|
||||
|
||||
// Heal blunt and slash back to 0
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -10, true));
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, -10, true));
|
||||
|
||||
// No new thresholds reached from healing
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 10
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
|
||||
|
||||
// No new thresholds reached as triggers once is set to true and it already triggered before
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
public class DestructibleDestructionTest : ContentIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
IEntity sDestructibleEntity = null;
|
||||
IDamageableComponent sDamageableComponent = 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(DestructibleDestructionEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var coordinates = sDestructibleEntity.Transform.Coordinates;
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 50, true));
|
||||
});
|
||||
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
var threshold = sThresholdListenerComponent.ThresholdsReached[0].Threshold;
|
||||
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
Assert.That(threshold.Behaviors.Count, Is.EqualTo(3));
|
||||
|
||||
var spawnEntitiesBehavior = (SpawnEntitiesBehavior) threshold.Behaviors.Single(b => b is SpawnEntitiesBehavior);
|
||||
|
||||
Assert.That(spawnEntitiesBehavior.Spawn.Count, Is.EqualTo(1));
|
||||
Assert.That(spawnEntitiesBehavior.Spawn.Keys.Single(), Is.EqualTo(SpawnedEntityId));
|
||||
Assert.That(spawnEntitiesBehavior.Spawn.Values.Single(), Is.EqualTo(new MinMax {Min = 1, Max = 1}));
|
||||
|
||||
var entitiesInRange = sEntityManager.GetEntitiesInRange(coordinates, 2);
|
||||
var found = false;
|
||||
|
||||
foreach (var entity in entitiesInRange)
|
||||
{
|
||||
if (entity.Prototype == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.Prototype.Name != SpawnedEntityId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Assert.That(found, Is.True);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
public static class DestructibleTestPrototypes
|
||||
{
|
||||
public const string SpawnedEntityId = "DestructibleTestsSpawnedEntity";
|
||||
public const string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
|
||||
public const string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
|
||||
public const string DestructibleDamageTypeEntityId = "DestructibleTestsDestructibleDamageTypeEntity";
|
||||
public const string DestructibleDamageClassEntityId = "DestructibleTestsDestructibleDamageClassEntity";
|
||||
|
||||
public static readonly string Prototypes = $@"
|
||||
- type: entity
|
||||
id: {SpawnedEntityId}
|
||||
name: {SpawnedEntityId}
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleEntityId}
|
||||
name: {DestructibleEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:TotalDamageTrigger
|
||||
damage: 20
|
||||
triggersOnce: false
|
||||
- trigger:
|
||||
!type:TotalDamageTrigger
|
||||
damage: 50
|
||||
triggersOnce: false
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound: /Audio/Effects/woodhit.ogg
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
{SpawnedEntityId}:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [""Breakage""]
|
||||
- type: TestThresholdListener
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleDestructionEntityId}
|
||||
name: {DestructibleDestructionEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:TotalDamageTrigger
|
||||
damage: 50
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound: /Audio/Effects/woodhit.ogg
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
{SpawnedEntityId}:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior # This must come last as it destroys the entity.
|
||||
acts: [""Destruction""]
|
||||
- type: TestThresholdListener
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleDamageTypeEntityId}
|
||||
name: {DestructibleDamageTypeEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:TotalDamageTypesTrigger
|
||||
damage:
|
||||
Blunt: 10
|
||||
Slash: 10
|
||||
- type: TestThresholdListener
|
||||
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleDamageClassEntityId}
|
||||
name: {DestructibleDamageClassEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:TotalDamageClassesTrigger
|
||||
damage:
|
||||
Brute: 10
|
||||
Burn: 10
|
||||
- type: TestThresholdListener";
|
||||
}
|
||||
}
|
||||
@@ -1,99 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Destructible;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behavior;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Behaviors;
|
||||
using Content.Server.GameObjects.Components.Destructible.Thresholds.Triggers;
|
||||
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;
|
||||
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DestructibleComponent))]
|
||||
[TestOf(typeof(Threshold))]
|
||||
public class DestructibleTests : ContentIntegrationTest
|
||||
public class DestructibleThresholdActivationTest : ContentIntegrationTest
|
||||
{
|
||||
private static readonly string SpawnedEntityId = "DestructibleTestsSpawnedEntity";
|
||||
private static readonly string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
|
||||
private static readonly string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
|
||||
|
||||
private static readonly string Prototypes = $@"
|
||||
- type: entity
|
||||
id: {SpawnedEntityId}
|
||||
name: {SpawnedEntityId}
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleEntityId}
|
||||
name: {DestructibleEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
20:
|
||||
triggersOnce: false
|
||||
50:
|
||||
triggersOnce: false
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound: /Audio/Effects/woodhit.ogg
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
{SpawnedEntityId}:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [""Breakage""]
|
||||
- type: TestThresholdListener
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleDestructionEntityId}
|
||||
name: {DestructibleDestructionEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
50:
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound: /Audio/Effects/woodhit.ogg
|
||||
- !type:SpawnEntitiesBehavior
|
||||
spawn:
|
||||
{SpawnedEntityId}:
|
||||
min: 1
|
||||
max: 1
|
||||
- !type:DoActsBehavior # This must come last as it destroys the entity.
|
||||
acts: [""Destruction""]
|
||||
- type: TestThresholdListener
|
||||
";
|
||||
|
||||
private class TestThresholdListenerComponent : Component
|
||||
{
|
||||
public override string Name => "TestThresholdListener";
|
||||
|
||||
public List<DestructibleThresholdReachedMessage> 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()
|
||||
public async Task Test()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
@@ -130,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.Zero);
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
@@ -138,36 +66,31 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
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.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
|
||||
|
||||
// Only one threshold reached, 20
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold 20
|
||||
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.Behaviors, Is.Empty);
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
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
|
||||
// One threshold reached, 50, since 20 already triggered before and it has not been healed below that amount
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold 50
|
||||
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
|
||||
@@ -184,6 +107,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
|
||||
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
|
||||
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
@@ -191,8 +115,19 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
// 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);
|
||||
// No thresholds reached as they weren't healed below the trigger amount
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
|
||||
// Heal down to 0
|
||||
sDamageableComponent.Heal();
|
||||
|
||||
// Damage for 100, up to 100
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 100, true));
|
||||
|
||||
// Two thresholds reached as damage increased past the previous, 20 and 50
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(2));
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Heal the entity for 40 damage, down to 60
|
||||
sDamageableComponent.ChangeDamage(DamageType.Blunt, -40, true);
|
||||
@@ -219,10 +154,6 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
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
|
||||
@@ -240,6 +171,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
|
||||
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
|
||||
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
// Reset thresholds reached
|
||||
@@ -259,10 +191,9 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
|
||||
// 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));
|
||||
var trigger = (TotalDamageTrigger) msg.Threshold.Trigger;
|
||||
Assert.NotNull(trigger);
|
||||
Assert.That(trigger.Damage, Is.EqualTo(20));
|
||||
|
||||
threshold = msg.Threshold;
|
||||
|
||||
@@ -271,10 +202,9 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
|
||||
// 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));
|
||||
trigger = (TotalDamageTrigger) msg.Threshold.Trigger;
|
||||
Assert.NotNull(trigger);
|
||||
Assert.That(trigger.Damage, Is.EqualTo(50));
|
||||
|
||||
threshold = msg.Threshold;
|
||||
|
||||
@@ -292,6 +222,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(spawnThreshold.Spawn.Single().Key, Is.EqualTo(SpawnedEntityId));
|
||||
Assert.That(spawnThreshold.Spawn.Single().Value.Min, Is.EqualTo(1));
|
||||
Assert.That(spawnThreshold.Spawn.Single().Value.Max, Is.EqualTo(1));
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
// Reset thresholds reached
|
||||
@@ -304,8 +235,9 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
|
||||
// Set both thresholds to only trigger once
|
||||
foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values)
|
||||
foreach (var destructibleThreshold in sDestructibleComponent.Thresholds)
|
||||
{
|
||||
Assert.NotNull(destructibleThreshold.Trigger);
|
||||
destructibleThreshold.TriggersOnce = true;
|
||||
}
|
||||
|
||||
@@ -319,8 +251,9 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Set both thresholds to trigger multiple times
|
||||
foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values)
|
||||
foreach (var destructibleThreshold in sDestructibleComponent.Thresholds)
|
||||
{
|
||||
Assert.NotNull(destructibleThreshold.Trigger);
|
||||
destructibleThreshold.TriggersOnce = false;
|
||||
}
|
||||
|
||||
@@ -331,84 +264,5 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DestructibleDestructionTest()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
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(DestructibleDestructionEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sDestructibleComponent = sDestructibleEntity.GetComponent<DestructibleComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var coordinates = sDestructibleEntity.Transform.Coordinates;
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 50, true));
|
||||
});
|
||||
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
var threshold = sThresholdListenerComponent.ThresholdsReached[0].Threshold;
|
||||
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
Assert.That(threshold.Behaviors.Count, Is.EqualTo(3));
|
||||
|
||||
var spawnEntitiesBehavior = (SpawnEntitiesBehavior) threshold.Behaviors.Single(b => b is SpawnEntitiesBehavior);
|
||||
|
||||
Assert.That(spawnEntitiesBehavior.Spawn.Count, Is.EqualTo(1));
|
||||
Assert.That(spawnEntitiesBehavior.Spawn.Keys.Single(), Is.EqualTo(SpawnedEntityId));
|
||||
Assert.That(spawnEntitiesBehavior.Spawn.Values.Single(), Is.EqualTo(new MinMax {Min = 1, Max = 1}));
|
||||
|
||||
var entitiesInRange = sEntityManager.GetEntitiesInRange(coordinates, 2);
|
||||
var found = false;
|
||||
|
||||
foreach (var entity in entitiesInRange)
|
||||
{
|
||||
if (entity.Prototype == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.Prototype.Name != SpawnedEntityId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Assert.That(found, Is.True);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Destructible;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
public class TestThresholdListenerComponent : Component
|
||||
{
|
||||
public override string Name => "TestThresholdListener";
|
||||
|
||||
public List<DestructibleThresholdReachedMessage> ThresholdsReached { get; } = new();
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DestructibleThresholdReachedMessage msg:
|
||||
ThresholdsReached.Add(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user