ECS damageable (#4529)
* ECS and damage Data * Comments and newlines * Added Comments * Make TryChangeDamageEvent immutable * Remove SetAllDamage event Use public SetAllDamage function instead * Undo destructible mistakes That was some shit code. * Rename DamageData to DamageSpecifier And misc small edits misc * Cache trigger prototypes. * Renaming destructible classes & functions * Revert "Cache trigger prototypes." This reverts commit 86bae15ba6616884dba75f552dfdfbe2d1fb6586. * Replace prototypes with prototype IDs. * Split damage.yml into individual files * move get/handle component state to system * Update HealthChange doc * Make godmode call Dirty() on damageable component * Add Initialize() to fix damage test * Make non-static * uncache resistance set prototype and trim DamageableComponentState * Remove unnecessary Dirty() calls during initialization * RemoveTryChangeDamageEvent * revert Dirty() * Fix MobState relying on DamageableComponent.Dirty() * Fix DisposalUnit Tests. These were previously failing, but because the async was not await-ed, this never raised the exception. After I fixed MobState component, this exception stopped happening and instead the assertions started being tested & failing * Disposal test 2: electric boogaloo * Fix typos/mistakes also add comments and fix spacing. * Use Uids instead of IEntity * fix merge * Comments, a merge issue, and making some damage ignore resistances * Extend DamageSpecifier and use it for DamageableComponent * fix master merge * Fix Disposal unit test. Again. Snapgrids were removed in master * Execute Exectute
This commit is contained in:
@@ -2,7 +2,7 @@ using System.Linq;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Mechanism;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -147,7 +147,7 @@ namespace Content.Client.Body.UI
|
||||
BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Owner.Name)}";
|
||||
|
||||
// TODO BODY Part damage
|
||||
if (part.Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (part.Owner.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
BodyPartHealth.Text = Loc.GetString("body-scanner-display-body-part-damage-text",("damage", damageable.TotalDamage));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.HealthOverlay.UI;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.MobState;
|
||||
using JetBrains.Annotations;
|
||||
@@ -79,7 +79,7 @@ namespace Content.Client.HealthOverlay
|
||||
|
||||
var viewBox = _eyeManager.GetWorldViewport().Enlarged(2.0f);
|
||||
|
||||
foreach (var (mobState, _) in ComponentManager.EntityQuery<IMobStateComponent, IDamageableComponent>())
|
||||
foreach (var (mobState, _) in ComponentManager.EntityQuery<IMobStateComponent, DamageableComponent>())
|
||||
{
|
||||
var entity = mobState.Owner;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.MobState;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -76,7 +76,7 @@ namespace Content.Client.HealthOverlay.UI
|
||||
}
|
||||
|
||||
if (!Entity.TryGetComponent(out IMobStateComponent? mobState) ||
|
||||
!Entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
!Entity.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
CritBar.Visible = false;
|
||||
HealthBar.Visible = false;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage;
|
||||
using System.Linq;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
@@ -10,6 +9,7 @@ using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
|
||||
namespace Content.Client.MedicalScanner.UI
|
||||
{
|
||||
@@ -55,59 +55,36 @@ namespace Content.Client.MedicalScanner.UI
|
||||
{
|
||||
text.Append($"{Loc.GetString("medical-scanner-window-entity-health-text", ("entityName", entity.Name))}\n");
|
||||
|
||||
// Show the total damage
|
||||
var totalDamage = state.DamagePerTypeID.Values.Sum();
|
||||
var totalDamage = state.DamagePerType.Values.Sum();
|
||||
text.Append($"{Loc.GetString("medical-scanner-window-entity-damage-total-text", ("amount", totalDamage))}\n");
|
||||
|
||||
// Keep track of how many damage types we have shown
|
||||
HashSet<string> shownTypeIDs = new();
|
||||
HashSet<string> shownTypes = new();
|
||||
|
||||
// First show just the total damage and type breakdown for each damge group that is fully supported by that entitygroup.
|
||||
foreach (var (damageGroupID, damageAmount) in state.DamagePerSupportedGroupID)
|
||||
// Show the total damage and type breakdown for each damage group.
|
||||
foreach (var (damageGroupID, damageAmount) in state.DamagePerGroup)
|
||||
{
|
||||
|
||||
// Show total damage for the group
|
||||
text.Append($"\n{Loc.GetString("medical-scanner-window-damage-group-text", ("damageGroup", damageGroupID), ("amount", damageAmount))}");
|
||||
|
||||
// Then show the damage for each type in that group.
|
||||
// currently state has a dictionary mapping groupsIDs to damage, and typeIDs to damage, but does not know how types and groups are related.
|
||||
// So use PrototypeManager.
|
||||
// Show the damage for each type in that group.
|
||||
var group = IoCManager.Resolve<IPrototypeManager>().Index<DamageGroupPrototype>(damageGroupID);
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
if (state.DamagePerTypeID.TryGetValue(type.ID, out var typeAmount))
|
||||
if (state.DamagePerType.TryGetValue(type, out var typeAmount))
|
||||
{
|
||||
// If damage types are allowed to belong to more than one damage group, they may appear twice here. Mark them as duplicate.
|
||||
if (!shownTypeIDs.Contains(type.ID))
|
||||
if (!shownTypes.Contains(type))
|
||||
{
|
||||
shownTypeIDs.Add(type.ID);
|
||||
text.Append($"\n- {Loc.GetString("medical-scanner-window-damage-type-text", ("damageType", type.ID), ("amount", typeAmount))}");
|
||||
shownTypes.Add(type);
|
||||
text.Append($"\n- {Loc.GetString("medical-scanner-window-damage-type-text", ("damageType", type), ("amount", typeAmount))}");
|
||||
}
|
||||
else {
|
||||
text.Append($"\n- {Loc.GetString("medical-scanner-window-damage-type-duplicate-text", ("damageType", type.ID), ("amount", typeAmount))}");
|
||||
text.Append($"\n- {Loc.GetString("medical-scanner-window-damage-type-duplicate-text", ("damageType", type), ("amount", typeAmount))}");
|
||||
}
|
||||
}
|
||||
}
|
||||
text.Append('\n');
|
||||
}
|
||||
|
||||
// Then, lets also list any damageType that was not fully Supported by the entity's damageContainer
|
||||
var textAppendix = new StringBuilder();
|
||||
int totalMiscDamage = 0;
|
||||
// Iterate over ids that have not been printed.
|
||||
foreach (var damageTypeID in state.DamagePerTypeID.Keys.Where(typeID => !shownTypeIDs.Contains(typeID)))
|
||||
{
|
||||
//This damage type was not yet added to the text.
|
||||
textAppendix.Append($"\n- {Loc.GetString("medical-scanner-window-damage-type-text", ("damageType", damageTypeID), ("amount", state.DamagePerTypeID[damageTypeID]))}");
|
||||
totalMiscDamage += state.DamagePerTypeID[damageTypeID];
|
||||
}
|
||||
|
||||
// Is there any information to show? Did any damage types not belong to a group?
|
||||
if (textAppendix.Length > 0) {
|
||||
text.Append($"\n{Loc.GetString("medical-scanner-window-damage-group-text", ("damageGroup", "Miscellaneous"), ("amount", totalMiscDamage))}");
|
||||
text.Append(textAppendix);
|
||||
}
|
||||
|
||||
_diagnostics.Text = text.ToString();
|
||||
ScanButton.Disabled = state.IsScanned;
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.MobState.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.MobState
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMobStateComponent))]
|
||||
[ComponentReference(typeof(IMobStateComponent))]
|
||||
public class MobStateComponent : SharedMobStateComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.MobState;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -21,7 +21,7 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
id: DamageableDummy
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
damageContainer: Biological
|
||||
- type: MobState
|
||||
thresholds:
|
||||
0: !type:NormalMobState {}
|
||||
@@ -47,15 +47,17 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
var human = entityManager.SpawnEntity("DamageableDummy", MapCoordinates.Nullspace);
|
||||
|
||||
// Sanity check
|
||||
Assert.True(human.TryGetComponent(out IDamageableComponent damageable));
|
||||
Assert.True(human.TryGetComponent(out DamageableComponent damageable));
|
||||
Assert.True(human.TryGetComponent(out IMobStateComponent mobState));
|
||||
mobState.UpdateState(0);
|
||||
Assert.That(mobState.IsAlive, Is.True);
|
||||
Assert.That(mobState.IsCritical, Is.False);
|
||||
Assert.That(mobState.IsDead, Is.False);
|
||||
Assert.That(mobState.IsIncapacitated, Is.False);
|
||||
|
||||
// Kill the entity
|
||||
damageable.TryChangeDamage(prototypeManager.Index<DamageGroupPrototype>("Toxin"), 10000000, true);
|
||||
DamageSpecifier damage = new(prototypeManager.Index<DamageGroupPrototype>("Toxin"), 10000000);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(human.Uid, damage, true);
|
||||
|
||||
// Check that it is dead
|
||||
Assert.That(mobState.IsAlive, Is.False);
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Damageable
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DamageableComponent))]
|
||||
public class AllSupportDamageableTest : ContentIntegrationTest
|
||||
{
|
||||
private const string AllDamageDamageableEntityId = "TestAllDamageDamageableEntityId";
|
||||
|
||||
/// <summary>
|
||||
/// Test a damageContainer with all types supported.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// As this should also loads in the damage groups & types in the actual damage.yml, this should also act as a basic test to see if damage.yml is set up properly.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task TestAllSupportDamageableComponent()
|
||||
{
|
||||
var server = StartServerDummyTicker();
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
IEntity sFullyDamageableEntity;
|
||||
IDamageableComponent sFullyDamageableComponent = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapId = sMapManager.NextMapId();
|
||||
var coordinates = new MapCoordinates(0, 0, mapId);
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
// When prototypes are loaded using the ExtraPrototypes option, they seem to be loaded first?
|
||||
// Or at least, no damage prototypes were loaded in by the time that the damageContainer here is loaded.
|
||||
// So for now doing explicit loading of prototypes.
|
||||
// I have no idea what I am doing, but it works.
|
||||
sPrototypeManager.LoadString($@"
|
||||
# we want to test the all damage container
|
||||
- type: damageContainer
|
||||
id: testAllDamageContainer
|
||||
supportAll: true
|
||||
|
||||
# create entities
|
||||
- type: entity
|
||||
id: {AllDamageDamageableEntityId}
|
||||
name: {AllDamageDamageableEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: testAllDamageContainer
|
||||
");
|
||||
|
||||
sFullyDamageableEntity = sEntityManager.SpawnEntity(AllDamageDamageableEntityId, coordinates);
|
||||
sFullyDamageableComponent = sFullyDamageableEntity.GetComponent<IDamageableComponent>();
|
||||
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
|
||||
// First check that there actually are any damage types/groups
|
||||
// This test depends on a non-empty damage.yml
|
||||
Assert.That(sPrototypeManager.EnumeratePrototypes<DamageTypePrototype>().ToList().Count, Is.GreaterThan(0));
|
||||
Assert.That(sPrototypeManager.EnumeratePrototypes<DamageGroupPrototype>().ToList().Count, Is.GreaterThan(0));
|
||||
|
||||
|
||||
// Can we set and get all damage.
|
||||
Assert.That(sFullyDamageableComponent.TrySetAllDamage(-10), Is.False);
|
||||
Assert.That(sFullyDamageableComponent.TrySetAllDamage(0), Is.True);
|
||||
|
||||
// Test that the all damage container supports every damage type, and that we can get, set, and change
|
||||
// every type with the expected results. Notable: if the damage does not change, they all return false
|
||||
var initialDamage = 10;
|
||||
foreach (var damageType in sPrototypeManager.EnumeratePrototypes<DamageTypePrototype>())
|
||||
{
|
||||
var damage = initialDamage;
|
||||
Assert.That(sFullyDamageableComponent.IsSupportedDamageType(damageType));
|
||||
Assert.That(sFullyDamageableComponent.TrySetDamage(damageType, -damage), Is.False);
|
||||
Assert.That(sFullyDamageableComponent.TrySetDamage(damageType, damage), Is.True);
|
||||
Assert.That(sFullyDamageableComponent.TrySetDamage(damageType, damage), Is.True); // intentional duplicate
|
||||
Assert.That(sFullyDamageableComponent.GetDamage(damageType), Is.EqualTo(damage));
|
||||
Assert.That(sFullyDamageableComponent.TryChangeDamage(damageType, -damage / 2, true), Is.True);
|
||||
Assert.That(sFullyDamageableComponent.TryGetDamage(damageType, out damage), Is.True);
|
||||
Assert.That(damage, Is.EqualTo(initialDamage/2));
|
||||
Assert.That(sFullyDamageableComponent.TryChangeDamage(damageType, damage, true), Is.True);
|
||||
Assert.That(sFullyDamageableComponent.GetDamage(damageType), Is.EqualTo(2* damage));
|
||||
Assert.That(sFullyDamageableComponent.TryChangeDamage(damageType, 0, true), Is.False);
|
||||
}
|
||||
// And again, for every group
|
||||
foreach (var damageGroup in sPrototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
var damage = initialDamage;
|
||||
var groupSize = damageGroup.DamageTypes.Count();
|
||||
Assert.That(sFullyDamageableComponent.IsFullySupportedDamageGroup(damageGroup));
|
||||
Assert.That(sFullyDamageableComponent.IsApplicableDamageGroup(damageGroup));
|
||||
Assert.That(sFullyDamageableComponent.TrySetDamage(damageGroup, -damage), Is.False);
|
||||
Assert.That(sFullyDamageableComponent.TrySetDamage(damageGroup, damage), Is.True);
|
||||
Assert.That(sFullyDamageableComponent.TrySetDamage(damageGroup, damage), Is.True); // intentional duplicate
|
||||
Assert.That(sFullyDamageableComponent.GetDamage(damageGroup), Is.EqualTo(damage * groupSize));
|
||||
Assert.That(sFullyDamageableComponent.TryChangeDamage(damageGroup, -groupSize*damage / 2, true), Is.True);
|
||||
Assert.That(sFullyDamageableComponent.TryGetDamage(damageGroup, out damage), Is.True);
|
||||
Assert.That(damage, Is.EqualTo(groupSize* initialDamage/2));
|
||||
Assert.That(sFullyDamageableComponent.TryChangeDamage(damageGroup, damage, true), Is.True);
|
||||
Assert.That(sFullyDamageableComponent.GetDamage(damageGroup), Is.EqualTo(2*damage));
|
||||
Assert.That(sFullyDamageableComponent.TryChangeDamage(damageGroup, 0, true), Is.False);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
@@ -11,75 +11,76 @@ namespace Content.IntegrationTests.Tests.Damageable
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DamageableComponent))]
|
||||
[TestOf(typeof(DamageableSystem))]
|
||||
public class DamageableTest : ContentIntegrationTest
|
||||
{
|
||||
private const string DamageableEntityId = "TestDamageableEntityId";
|
||||
private const string Group1Id = "TestGroup1";
|
||||
private const string Group2Id = "TestGroup2";
|
||||
private const string Group3Id = "TestGroup3";
|
||||
private string Prototypes = $@"
|
||||
private const string Prototypes = @"
|
||||
# Define some damage groups
|
||||
- type: damageType
|
||||
id: TestDamage11
|
||||
id: TestDamage1
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage21
|
||||
id: TestDamage2a
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage22
|
||||
id: TestDamage2b
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage31
|
||||
id: TestDamage3a
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage32
|
||||
id: TestDamage3b
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage33
|
||||
id: TestDamage3c
|
||||
|
||||
# Define damage Groups with 1,2,3 damage types
|
||||
- type: damageGroup
|
||||
id: {Group1Id}
|
||||
id: TestGroup1
|
||||
damageTypes:
|
||||
- TestDamage11
|
||||
- TestDamage1
|
||||
|
||||
- type: damageGroup
|
||||
id: {Group2Id}
|
||||
id: TestGroup2
|
||||
damageTypes:
|
||||
- TestDamage21
|
||||
- TestDamage22
|
||||
- TestDamage2a
|
||||
- TestDamage2b
|
||||
|
||||
- type: damageGroup
|
||||
id: {Group3Id}
|
||||
id: TestGroup3
|
||||
damageTypes:
|
||||
- TestDamage31
|
||||
- TestDamage32
|
||||
- TestDamage33
|
||||
- TestDamage3a
|
||||
- TestDamage3b
|
||||
- TestDamage3c
|
||||
|
||||
# we want to test a container that supports only full groups
|
||||
# we will also give full support for group 2 IMPLICITLY by specifying all of its members are supported.
|
||||
- type: resistanceSet
|
||||
id: testResistances
|
||||
# this space is intentionally left blank
|
||||
|
||||
# This container should not support TestDamage1 or TestDamage2b
|
||||
- type: damageContainer
|
||||
id: testSomeDamageContainer
|
||||
id: testDamageContainer
|
||||
defaultResistanceSet: testResistances
|
||||
supportedGroups:
|
||||
- {Group3Id}
|
||||
- TestGroup3
|
||||
supportedTypes:
|
||||
- TestDamage21
|
||||
- TestDamage22
|
||||
- TestDamage2a
|
||||
|
||||
- type: entity
|
||||
id: {DamageableEntityId}
|
||||
name: {DamageableEntityId}
|
||||
id: TestDamageableEntityId
|
||||
name: TestDamageableEntityId
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: testSomeDamageContainer
|
||||
damageContainer: testDamageContainer
|
||||
";
|
||||
|
||||
/// <summary>
|
||||
/// Test a standard damageable components
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only test scenarios where each damage type is a member of exactly one group, and all damageable components support whole groups, not lone damage types.
|
||||
/// </remarks>
|
||||
// public bool & function to determine whether dealing damage resulted in actual damage change
|
||||
public bool DamageChanged = false;
|
||||
public void DamageChangedListener(EntityUid _, DamageableComponent comp, DamageChangedEvent args)
|
||||
{
|
||||
DamageChanged = true;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestDamageableComponents()
|
||||
{
|
||||
@@ -93,360 +94,150 @@ namespace Content.IntegrationTests.Tests.Damageable
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var sEntitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
|
||||
IEntity sDamageableEntity;
|
||||
IDamageableComponent sDamageableComponent = null;
|
||||
sEntityManager.EventBus.SubscribeLocalEvent<DamageableComponent, DamageChangedEvent>(DamageChangedListener);
|
||||
|
||||
IEntity sDamageableEntity = null;
|
||||
DamageableComponent sDamageableComponent = null;
|
||||
DamageableSystem sDamageableSystem = null;
|
||||
|
||||
DamageGroupPrototype group1 = default!;
|
||||
DamageGroupPrototype group2 = default!;
|
||||
DamageGroupPrototype group3 = default!;
|
||||
|
||||
DamageTypePrototype type1 = default!;
|
||||
DamageTypePrototype type2a = default!;
|
||||
DamageTypePrototype type2b = default!;
|
||||
DamageTypePrototype type3a = default!;
|
||||
DamageTypePrototype type3b = default!;
|
||||
DamageTypePrototype type3c = default!;
|
||||
|
||||
int typeDamage, groupDamage;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapId = sMapManager.NextMapId();
|
||||
var coordinates = new MapCoordinates(0, 0, mapId);
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
sDamageableEntity = sEntityManager.SpawnEntity(DamageableEntityId, coordinates);
|
||||
sDamageableComponent = sDamageableEntity.GetComponent<IDamageableComponent>();
|
||||
sDamageableEntity = sEntityManager.SpawnEntity("TestDamageableEntityId", coordinates);
|
||||
sDamageableComponent = sDamageableEntity.GetComponent<DamageableComponent>();
|
||||
sDamageableSystem = sEntitySystemManager.GetEntitySystem<DamageableSystem>();
|
||||
|
||||
group1 = sPrototypeManager.Index<DamageGroupPrototype>(Group1Id);
|
||||
group2 = sPrototypeManager.Index<DamageGroupPrototype>(Group2Id);
|
||||
group3 = sPrototypeManager.Index<DamageGroupPrototype>(Group3Id);
|
||||
group1 = sPrototypeManager.Index<DamageGroupPrototype>("TestGroup1");
|
||||
group2 = sPrototypeManager.Index<DamageGroupPrototype>("TestGroup2");
|
||||
group3 = sPrototypeManager.Index<DamageGroupPrototype>("TestGroup3");
|
||||
|
||||
type1 = sPrototypeManager.Index<DamageTypePrototype>("TestDamage1");
|
||||
type2a = sPrototypeManager.Index<DamageTypePrototype>("TestDamage2a");
|
||||
type2b = sPrototypeManager.Index<DamageTypePrototype>("TestDamage2b");
|
||||
type3a = sPrototypeManager.Index<DamageTypePrototype>("TestDamage3a");
|
||||
type3b = sPrototypeManager.Index<DamageTypePrototype>("TestDamage3b");
|
||||
type3c = sPrototypeManager.Index<DamageTypePrototype>("TestDamage3c");
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Check that the correct groups are supported by the container
|
||||
Assert.That(sDamageableComponent.IsApplicableDamageGroup(group1), Is.False);
|
||||
Assert.That(sDamageableComponent.IsApplicableDamageGroup(group2), Is.True);
|
||||
Assert.That(sDamageableComponent.IsApplicableDamageGroup(group3), Is.True);
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group1), Is.False);
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group2), Is.True);
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group3), Is.True);
|
||||
var uid = sDamageableEntity.Uid;
|
||||
|
||||
// Check that the correct types are supported:
|
||||
foreach (var group in sPrototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
foreach(var type in group.DamageTypes)
|
||||
{
|
||||
if (sDamageableComponent.IsFullySupportedDamageGroup(group))
|
||||
{
|
||||
Assert.That(sDamageableComponent.IsSupportedDamageType(type), Is.True);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(sDamageableComponent.IsSupportedDamageType(type), Is.False);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check that the correct types are supported.
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.ContainsKey(type1.ID), Is.False);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.ContainsKey(type2a.ID), Is.True);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.ContainsKey(type2b.ID), Is.False);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.ContainsKey(type3a.ID), Is.True);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.ContainsKey(type3b.ID), Is.True);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.ContainsKey(type3c.ID), Is.True);
|
||||
|
||||
// Check that damage is evenly distributed over a group if its a nice multiple
|
||||
var types = group3.DamageTypes;
|
||||
var damageToDeal = types.Count() * 5;
|
||||
DamageSpecifier damage = new(group3, damageToDeal);
|
||||
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group1), Is.False);
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group2), Is.True);
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group3), Is.True);
|
||||
|
||||
// Check that damage works properly if perfectly divisible among group members
|
||||
int damageToDeal, groupDamage, typeDamage; ;
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
foreach (var damageGroup in sDamageableComponent.FullySupportedDamageGroups)
|
||||
{
|
||||
var types = damageGroup.DamageTypes;
|
||||
|
||||
// Damage
|
||||
damageToDeal = types.Count() * 5;
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, damageToDeal, true), Is.True);
|
||||
sDamageableSystem.TryChangeDamage(uid, damage, true);
|
||||
Assert.That(DamageChanged);
|
||||
DamageChanged = false;
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damageToDeal));
|
||||
Assert.That(sDamageableComponent.TryGetDamage(damageGroup, out groupDamage), Is.True);
|
||||
Assert.That(groupDamage, Is.EqualTo(damageToDeal));
|
||||
|
||||
Assert.That(sDamageableComponent.DamagePerGroup[group3.ID], Is.EqualTo(damageToDeal));
|
||||
foreach (var type in types)
|
||||
{
|
||||
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.True);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.TryGetValue(type, out typeDamage));
|
||||
Assert.That(typeDamage, Is.EqualTo(damageToDeal / types.Count()));
|
||||
}
|
||||
|
||||
// Heal
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, -damageToDeal, true), Is.True);
|
||||
sDamageableSystem.TryChangeDamage(uid, -damage);
|
||||
Assert.That(DamageChanged);
|
||||
DamageChanged = false;
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.Zero);
|
||||
Assert.That(sDamageableComponent.TryGetDamage(damageGroup, out groupDamage), Is.True);
|
||||
Assert.That(groupDamage, Is.Zero);
|
||||
|
||||
Assert.That(sDamageableComponent.DamagePerGroup[group3.ID], Is.EqualTo(0));
|
||||
foreach (var type in types)
|
||||
{
|
||||
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.True);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.TryGetValue(type, out typeDamage));
|
||||
Assert.That(typeDamage, Is.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that damage works properly if it is NOT perfectly divisible among group members
|
||||
foreach (var damageGroup in sDamageableComponent.FullySupportedDamageGroups)
|
||||
{
|
||||
var types = damageGroup.DamageTypes;
|
||||
|
||||
// Damage
|
||||
types = group3.DamageTypes;
|
||||
damageToDeal = types.Count() * 5 - 1;
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, damageToDeal, true), Is.True);
|
||||
damage = new DamageSpecifier(group3, damageToDeal);
|
||||
sDamageableSystem.TryChangeDamage(uid, damage, true);
|
||||
Assert.That(DamageChanged);
|
||||
DamageChanged = false;
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damageToDeal));
|
||||
Assert.That(sDamageableComponent.TryGetDamage(damageGroup, out groupDamage), Is.True);
|
||||
Assert.That(groupDamage, Is.EqualTo(damageToDeal));
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.True);
|
||||
float targetDamage = ((float) damageToDeal) / types.Count();
|
||||
Assert.That(typeDamage, Is.InRange(targetDamage - 1, targetDamage + 1));
|
||||
}
|
||||
Assert.That(sDamageableComponent.DamagePerGroup[group3.ID], Is.EqualTo(damageToDeal));
|
||||
// integer rounding. In this case, first member gets 1 less than others.
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict[type3a.ID], Is.EqualTo(damageToDeal / types.Count()));
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict[type3b.ID], Is.EqualTo(1 + damageToDeal / types.Count()));
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict[type3c.ID], Is.EqualTo(1 + damageToDeal / types.Count()));
|
||||
|
||||
// Heal
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, -damageToDeal, true), Is.True);
|
||||
sDamageableSystem.TryChangeDamage(uid, -damage);
|
||||
Assert.That(DamageChanged);
|
||||
DamageChanged = false;
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.Zero);
|
||||
Assert.That(sDamageableComponent.TryGetDamage(damageGroup, out groupDamage), Is.True);
|
||||
Assert.That(groupDamage, Is.Zero);
|
||||
|
||||
Assert.That(sDamageableComponent.DamagePerGroup[group3.ID], Is.EqualTo(0));
|
||||
foreach (var type in types)
|
||||
{
|
||||
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.True);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.TryGetValue(type, out typeDamage));
|
||||
Assert.That(typeDamage, Is.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
// Test that unsupported groups return false when setting/getting damage (and don't change damage)
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
foreach (var damageGroup in sPrototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
if (sDamageableComponent.IsFullySupportedDamageGroup(damageGroup))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.That(sDamageableComponent.IsApplicableDamageGroup(damageGroup), Is.False);
|
||||
|
||||
var types = damageGroup.DamageTypes;
|
||||
damageToDeal = types.Count() * 5;
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
Assert.That(sDamageableComponent.IsSupportedDamageType(type), Is.False);
|
||||
}
|
||||
;
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, damageToDeal, true), Is.False);
|
||||
Assert.That(sDamageableComponent.TryGetDamage(damageGroup, out groupDamage), Is.False);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(type, damageToDeal, true), Is.False);
|
||||
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.False);
|
||||
}
|
||||
}
|
||||
// Did damage change?
|
||||
damage = new DamageSpecifier(group1, 10) + new DamageSpecifier(type2b, 10);
|
||||
sDamageableSystem.TryChangeDamage(uid, damage, true);
|
||||
Assert.That(DamageChanged, Is.False);
|
||||
Assert.That(sDamageableComponent.DamagePerGroup.TryGetValue(group1.ID, out groupDamage), Is.False);
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict.TryGetValue(type1.ID, out typeDamage), Is.False);
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
|
||||
|
||||
// Test total damage function
|
||||
damageToDeal = 10;
|
||||
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(group3, damageToDeal, true));
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damageToDeal));
|
||||
|
||||
var totalTypeDamage = 0;
|
||||
|
||||
foreach (var damageType in sDamageableComponent.SupportedDamageTypes)
|
||||
{
|
||||
Assert.True(sDamageableComponent.TryGetDamage(damageType, out typeDamage));
|
||||
Assert.That(typeDamage, Is.LessThanOrEqualTo(damageToDeal));
|
||||
|
||||
totalTypeDamage += typeDamage;
|
||||
}
|
||||
Assert.That(totalTypeDamage, Is.EqualTo(damageToDeal));
|
||||
|
||||
|
||||
// Test healing all damage
|
||||
Assert.That(sDamageableComponent.TrySetAllDamage(0));
|
||||
// Test SetAll function
|
||||
sDamageableSystem.SetAllDamage(sDamageableComponent, 10);
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(10 * sDamageableComponent.Damage.DamageDict.Count()));
|
||||
sDamageableSystem.SetAllDamage(sDamageableComponent, 0);
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
|
||||
// Test preferential healing
|
||||
damageToDeal = 12;
|
||||
var damageTypes = group3.DamageTypes.ToArray();
|
||||
// Test 'wasted' healing
|
||||
sDamageableSystem.TryChangeDamage(uid, new DamageSpecifier(type3a, 5));
|
||||
sDamageableSystem.TryChangeDamage(uid, new DamageSpecifier(type3b, 7));
|
||||
sDamageableSystem.TryChangeDamage(uid, new DamageSpecifier(group3, -11));
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict[type3a.ID], Is.EqualTo(2));
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict[type3b.ID], Is.EqualTo(3));
|
||||
Assert.That(sDamageableComponent.Damage.DamageDict[type3c.ID], Is.EqualTo(0));
|
||||
|
||||
// Deal damage
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(damageTypes[0], 17));
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(damageTypes[1], 31));
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(48));
|
||||
|
||||
// Heal group damage
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(group3, -11));
|
||||
|
||||
// Check healing (3 + 9)
|
||||
Assert.That(sDamageableComponent.GetDamage(damageTypes[0]), Is.EqualTo(14));
|
||||
Assert.That(sDamageableComponent.GetDamage(damageTypes[1]), Is.EqualTo(23));
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(37));
|
||||
|
||||
// Heal group damage
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(group3, -36));
|
||||
|
||||
// Check healing (13 + 23)
|
||||
Assert.That(sDamageableComponent.GetDamage(damageTypes[0]), Is.EqualTo(1));
|
||||
Assert.That(sDamageableComponent.GetDamage(damageTypes[1]), Is.EqualTo(0));
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(1));
|
||||
|
||||
//Check Damage
|
||||
Assert.True(sDamageableComponent.TryGetDamage(damageTypes[0], out typeDamage));
|
||||
Assert.That(typeDamage, Is.LessThanOrEqualTo(damageToDeal));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private const string SharedDamageTypeId = "TestSharedDamage";
|
||||
private const string UnsupportedDamageTypeId = "TestUnsupportedDamage";
|
||||
private string Prototypes2 = $@"
|
||||
- type: damageType
|
||||
id: {SharedDamageTypeId}
|
||||
|
||||
- type: damageType
|
||||
id: {UnsupportedDamageTypeId}
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage1
|
||||
|
||||
- type: damageType
|
||||
id: TestDamage2
|
||||
|
||||
- type: damageGroup
|
||||
id: {Group1Id}
|
||||
damageTypes:
|
||||
- {SharedDamageTypeId}
|
||||
|
||||
- type: damageGroup
|
||||
id: {Group2Id}
|
||||
damageTypes:
|
||||
- {SharedDamageTypeId}
|
||||
- TestDamage1
|
||||
|
||||
- type: damageGroup
|
||||
id: {Group3Id}
|
||||
damageTypes:
|
||||
- {SharedDamageTypeId}
|
||||
- TestDamage2
|
||||
- {UnsupportedDamageTypeId}
|
||||
|
||||
# we want to test a container that only partially supports a group:
|
||||
- type: damageContainer
|
||||
id: TestPartiallySupported
|
||||
supportedGroups:
|
||||
- {Group2Id}
|
||||
supportedTypes:
|
||||
- TestDamage2
|
||||
- TestDamage1
|
||||
# does NOT support type {UnsupportedDamageTypeId}, and thus does not fully support group {Group3Id}
|
||||
# TestDamage1 is added twice because it is also in {Group2Id}. This should not cause errors.
|
||||
|
||||
# create entities
|
||||
- type: entity
|
||||
id: {DamageableEntityId}
|
||||
name: {DamageableEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: TestPartiallySupported
|
||||
";
|
||||
|
||||
/// <summary>
|
||||
/// Generalized damageable component tests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Test scenarios where damage types are members of more than one group, or where a component only supports a subset of a group.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task TestGeneralizedDamageableComponent()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes2
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
IEntity sDamageableEntity;
|
||||
IDamageableComponent sDamageableComponent = null;
|
||||
|
||||
DamageGroupPrototype group1 = default!;
|
||||
DamageGroupPrototype group2 = default!;
|
||||
DamageGroupPrototype group3 = default!;
|
||||
|
||||
DamageTypePrototype SharedDamageType = default!;
|
||||
DamageTypePrototype UnsupportedDamageType = default!;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapId = sMapManager.NextMapId();
|
||||
var coordinates = new MapCoordinates(0, 0, mapId);
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
sDamageableEntity = sEntityManager.SpawnEntity(DamageableEntityId, coordinates);
|
||||
sDamageableComponent = sDamageableEntity.GetComponent<IDamageableComponent>();
|
||||
|
||||
group1 = sPrototypeManager.Index<DamageGroupPrototype>(Group1Id);
|
||||
group2 = sPrototypeManager.Index<DamageGroupPrototype>(Group2Id);
|
||||
group3 = sPrototypeManager.Index<DamageGroupPrototype>(Group3Id);
|
||||
|
||||
SharedDamageType = sPrototypeManager.Index<DamageTypePrototype>(SharedDamageTypeId);
|
||||
UnsupportedDamageType = sPrototypeManager.Index<DamageTypePrototype>(UnsupportedDamageTypeId);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// All damage types should be applicable
|
||||
Assert.That(sDamageableComponent.IsApplicableDamageGroup(group1), Is.True);
|
||||
Assert.That(sDamageableComponent.IsApplicableDamageGroup(group2), Is.True);
|
||||
Assert.That(sDamageableComponent.IsApplicableDamageGroup(group3), Is.True);
|
||||
|
||||
// But not all should be fully supported
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group1), Is.True);
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group2), Is.True);
|
||||
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group3), Is.False);
|
||||
|
||||
// Check that the correct damage types are supported
|
||||
Assert.That(sDamageableComponent.IsSupportedDamageType(SharedDamageType), Is.True);
|
||||
|
||||
// Check that if we deal damage using a type appearing in multiple groups, nothing goes wrong.
|
||||
var damage = 12;
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(SharedDamageType, damage), Is.True);
|
||||
Assert.That(sDamageableComponent.GetDamage(SharedDamageType), Is.EqualTo(damage));
|
||||
Assert.That(sDamageableComponent.GetDamage(group1), Is.EqualTo(damage));
|
||||
Assert.That(sDamageableComponent.GetDamage(group2), Is.EqualTo(damage));
|
||||
Assert.That(sDamageableComponent.GetDamage(group3), Is.EqualTo(damage));
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damage));
|
||||
|
||||
// Check that if we deal damage using a group that is not fully supported, the damage is reduced
|
||||
// Note that if damage2 were not neatly divisible by 3, the actual damage reduction would be subject to integer rounding.
|
||||
// How much exactly the damage gets reduced then would depend on the order that the groups were defined in the yaml file
|
||||
// Here we deal 9 damage. It should apply 3 damage to each type, but one type is ignored, resulting in 6 total damage.
|
||||
// However, the damage in group2 and group3 only changes because of one type that overlaps, so they only change by 3
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(group3, 9), Is.True);
|
||||
Assert.That(sDamageableComponent.GetDamage(group1), Is.EqualTo(damage + 3));
|
||||
Assert.That(sDamageableComponent.GetDamage(group2), Is.EqualTo(damage + 3));
|
||||
Assert.That(sDamageableComponent.GetDamage(group3), Is.EqualTo(damage + 6));
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damage + 6));
|
||||
|
||||
// Now we check that when healing, no damage is wasted.
|
||||
// Because SharedDamageType has the most damage in group3 (15 vs 3), it will be healed more than the other.
|
||||
// Expect that, up to integer rounding, one is healed 5* more than the other.
|
||||
// We will use a number that does not divide nicely, there will be some integer rounding.
|
||||
Assert.That(sDamageableComponent.TryChangeDamage(group3, -7), Is.True);
|
||||
Assert.That(sDamageableComponent.GetDamage(group1), Is.EqualTo(damage + 3 - 5));
|
||||
Assert.That(sDamageableComponent.GetDamage(group2), Is.EqualTo(damage + 3 - 5));
|
||||
Assert.That(sDamageableComponent.GetDamage(group3), Is.EqualTo(damage + 6 - 7));
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damage + 6 - 7));
|
||||
// Test Over-Healing
|
||||
sDamageableSystem.TryChangeDamage(uid, new DamageSpecifier(group3, -100));
|
||||
Assert.That(DamageChanged);
|
||||
DamageChanged = false;
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
|
||||
// Test that if no health change occurred, returns false
|
||||
sDamageableSystem.TryChangeDamage(uid, new DamageSpecifier(group3, -100));
|
||||
Assert.That(DamageChanged, Is.False);
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -21,11 +21,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().RegisterClass<TestThresholdListenerComponent>();
|
||||
}
|
||||
ExtraPrototypes = Prototypes
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
@@ -33,10 +29,12 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var sEntitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
|
||||
IEntity sDestructibleEntity;
|
||||
IDamageableComponent sDamageableComponent = null;
|
||||
TestThresholdListenerComponent sThresholdListenerComponent = null;
|
||||
IEntity sDestructibleEntity = null;
|
||||
DamageableComponent sDamageableComponent = null;
|
||||
TestDestructibleListenerSystem sTestThresholdListenerSystem = null;
|
||||
DamageableSystem sDamageableSystem = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
@@ -45,15 +43,16 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDamageGroupEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<DamageableComponent>();
|
||||
sTestThresholdListenerSystem = sEntitySystemManager.GetEntitySystem<TestDestructibleListenerSystem>();
|
||||
sDamageableSystem = sEntitySystemManager.GetEntitySystem<DamageableSystem>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
@@ -61,26 +60,29 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBrute");
|
||||
var burnDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBurn");
|
||||
|
||||
DamageSpecifier bruteDamage = new(bruteDamageGroup,5);
|
||||
DamageSpecifier burnDamage = new(burnDamageGroup,5);
|
||||
|
||||
// Raise brute damage to 5
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 5, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bruteDamage, true);
|
||||
|
||||
// No thresholds reached yet, the earliest one is at 10 damage
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise brute damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 5, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bruteDamage, true);
|
||||
|
||||
// No threshold reached, burn needs to be 10 as well
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, burnDamage * 2, true);
|
||||
|
||||
// One threshold reached, brute 10 + burn 10
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold brute 10 + burn 10
|
||||
var msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
var msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
var threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
@@ -94,55 +96,55 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[0]);
|
||||
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[1]);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Raise brute damage to 20
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bruteDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 20
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, burnDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Lower brute damage to 0
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, -20, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bruteDamage * -10);
|
||||
Assert.That(sDamageableComponent.TotalDamage,Is.EqualTo(20));
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise brute damage back up to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bruteDamage * 2, true);
|
||||
|
||||
// 10 brute + 10 burn threshold reached, brute was healed and brought back to its threshold amount and burn stayed the same
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
// 10 brute + 10 burn threshold reached, brute was healed and brought back to its threshold amount and slash stayed the same
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Heal both classes of damage to 0
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, -10, true));
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, -20, true));
|
||||
sDamageableSystem.SetAllDamage(sDamageableComponent, 0);
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise brute damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bruteDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, burnDamage * 2, 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));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold brute 10 + burn 10
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
@@ -156,29 +158,28 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[0]);
|
||||
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[1]);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Change triggers once to true
|
||||
threshold.TriggersOnce = true;
|
||||
|
||||
// Heal brute and burn back to 0
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, -10, true));
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, -10, true));
|
||||
sDamageableSystem.SetAllDamage(sDamageableComponent, 0);
|
||||
|
||||
// No new thresholds reached from healing
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise brute damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bruteDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise burn damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, burnDamage * 2, true);
|
||||
|
||||
// No new thresholds reached as triggers once is set to true and it already triggered before
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -21,21 +21,19 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().RegisterClass<TestThresholdListenerComponent>();
|
||||
}
|
||||
ExtraPrototypes = Prototypes
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sEntitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
|
||||
IEntity sDestructibleEntity;
|
||||
IDamageableComponent sDamageableComponent = null;
|
||||
TestThresholdListenerComponent sThresholdListenerComponent = null;
|
||||
IEntity sDestructibleEntity = null;
|
||||
DamageableComponent sDamageableComponent = null;
|
||||
TestDestructibleListenerSystem sTestThresholdListenerSystem = null;
|
||||
DamageableSystem sDamageableSystem = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
@@ -44,15 +42,16 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDamageTypeEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<DamageableComponent>();
|
||||
sTestThresholdListenerSystem = sEntitySystemManager.GetEntitySystem<TestDestructibleListenerSystem>();
|
||||
sDamageableSystem = sEntitySystemManager.GetEntitySystem<DamageableSystem>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
@@ -60,26 +59,29 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
var bluntDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("TestBlunt");
|
||||
var slashDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("TestSlash");
|
||||
|
||||
var bluntDamage = new DamageSpecifier(bluntDamageType,5);
|
||||
var slashDamage = new DamageSpecifier(slashDamageType,5);
|
||||
|
||||
// Raise blunt damage to 5
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 5, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage, true);
|
||||
|
||||
// No thresholds reached yet, the earliest one is at 10 damage
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 5, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage, true);
|
||||
|
||||
// No threshold reached, slash needs to be 10 as well
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, slashDamage * 2, true);
|
||||
|
||||
// One threshold reached, blunt 10 + slash 10
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold blunt 10 + slash 10
|
||||
var msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
var msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
var threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
@@ -93,55 +95,55 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.IsInstanceOf<DamageTypeTrigger>(trigger.Triggers[0]);
|
||||
Assert.IsInstanceOf<DamageTypeTrigger>(trigger.Triggers[1]);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Raise blunt damage to 20
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 20
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, slashDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Lower blunt damage to 0
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, -20, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage * -4, true);
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage back up to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage * 2, 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));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Heal both types of damage to 0
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, -10, true));
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, -20, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage * -2, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, slashDamage * -4, true);
|
||||
|
||||
// No new thresholds reached, healing should not trigger it
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, slashDamage * 2, 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));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold blunt 10 + slash 10
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
@@ -155,29 +157,29 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.IsInstanceOf<DamageTypeTrigger>(trigger.Triggers[0]);
|
||||
Assert.IsInstanceOf<DamageTypeTrigger>(trigger.Triggers[1]);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Change triggers once to true
|
||||
threshold.TriggersOnce = true;
|
||||
|
||||
// Heal blunt and slash back to 0
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, -10, true));
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, -10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage * -2, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, slashDamage * -2, true);
|
||||
|
||||
// No new thresholds reached from healing
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise blunt damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage * 2, true);
|
||||
|
||||
// No new thresholds reached
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Raise slash damage to 10
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, slashDamage * 2, true);
|
||||
|
||||
// No new thresholds reached as triggers once is set to true and it already triggered before
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Threading.Tasks;
|
||||
using Content.Server.Destructible.Thresholds;
|
||||
using Content.Server.Destructible.Thresholds.Behaviors;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -20,11 +20,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().RegisterClass<TestThresholdListenerComponent>();
|
||||
}
|
||||
ExtraPrototypes = Prototypes
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
@@ -32,10 +28,11 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var sEntitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
|
||||
IEntity sDestructibleEntity = null;
|
||||
IDamageableComponent sDamageableComponent = null;
|
||||
TestThresholdListenerComponent sThresholdListenerComponent = null;
|
||||
DamageableComponent sDamageableComponent = null;
|
||||
TestDestructibleListenerSystem sTestThresholdListenerSystem = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
@@ -44,23 +41,24 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDestructionEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<DamageableComponent>();
|
||||
sTestThresholdListenerSystem = sEntitySystemManager.GetEntitySystem<TestDestructibleListenerSystem>();
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var coordinates = sDestructibleEntity.Transform.Coordinates;
|
||||
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBrute");
|
||||
DamageSpecifier bruteDamage = new(bruteDamageGroup,50);
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 50, true));
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(sDestructibleEntity.Uid, bruteDamage, true);
|
||||
});
|
||||
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
var threshold = sThresholdListenerComponent.ThresholdsReached[0].Threshold;
|
||||
var threshold = sTestThresholdListenerSystem.ThresholdsReached[0].Threshold;
|
||||
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
Assert.That(threshold.Behaviors.Count, Is.EqualTo(3));
|
||||
|
||||
@@ -27,21 +27,6 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
- type: damageType
|
||||
id: TestCold
|
||||
|
||||
- type: damageType
|
||||
id: TestPoison
|
||||
|
||||
- type: damageType
|
||||
id: TestRadiation
|
||||
|
||||
- type: damageType
|
||||
id: TestAsphyxiation
|
||||
|
||||
- type: damageType
|
||||
id: TestBloodloss
|
||||
|
||||
- type: damageType
|
||||
id: TestCellular
|
||||
|
||||
- type: damageGroup
|
||||
id: TestBrute
|
||||
damageTypes:
|
||||
@@ -56,43 +41,6 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
- TestShock
|
||||
- TestCold
|
||||
|
||||
- type: damageGroup
|
||||
id: TestAirloss
|
||||
damageTypes:
|
||||
- TestAsphyxiation
|
||||
- TestBloodloss
|
||||
|
||||
- type: damageGroup
|
||||
id: TestToxin
|
||||
damageTypes:
|
||||
- TestPoison
|
||||
- TestRadiation
|
||||
|
||||
- type: damageGroup
|
||||
id: TestGenetic
|
||||
damageTypes:
|
||||
- TestCellular
|
||||
|
||||
- type: damageContainer
|
||||
id: TestAllDamageContainer
|
||||
supportAll: true
|
||||
|
||||
|
||||
- type: damageContainer
|
||||
id: TestBiologicalDamageContainer
|
||||
supportedGroups:
|
||||
- TestBrute
|
||||
- TestBurn
|
||||
- TestToxin
|
||||
- TestAirloss
|
||||
- TestGenetic
|
||||
|
||||
- type: damageContainer
|
||||
id: TestMetallicDamageContainer
|
||||
supportedGroups:
|
||||
- TestBrute
|
||||
- TestBurn
|
||||
|
||||
- type: entity
|
||||
id: {SpawnedEntityId}
|
||||
name: {SpawnedEntityId}
|
||||
@@ -102,7 +50,6 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
name: {DestructibleEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: TestMetallicDamageContainer
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -124,14 +71,12 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [""Breakage""]
|
||||
- type: TestThresholdListener
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleDestructionEntityId}
|
||||
name: {DestructibleDestructionEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: TestMetallicDamageContainer
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -148,14 +93,12 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
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
|
||||
damageContainer: TestMetallicDamageContainer
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -167,14 +110,12 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
- !type:DamageTypeTrigger
|
||||
damageType: TestSlash
|
||||
damage: 10
|
||||
- type: TestThresholdListener
|
||||
|
||||
- type: entity
|
||||
id: {DestructibleDamageGroupEntityId}
|
||||
name: {DestructibleDamageGroupEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
damageContainer: TestMetallicDamageContainer
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -185,7 +126,6 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
damage: 10
|
||||
- !type:DamageGroupTrigger
|
||||
damageGroup: TestBurn
|
||||
damage: 10
|
||||
- type: TestThresholdListener";
|
||||
damage: 10";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using Content.Server.Destructible.Thresholds;
|
||||
using Content.Server.Destructible.Thresholds.Behaviors;
|
||||
using Content.Server.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -17,7 +17,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DestructibleComponent))]
|
||||
[TestOf(typeof(Threshold))]
|
||||
[TestOf(typeof(DamageThreshold))]
|
||||
public class DestructibleThresholdActivationTest : ContentIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
@@ -25,11 +25,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().RegisterClass<TestThresholdListenerComponent>();
|
||||
}
|
||||
ExtraPrototypes = Prototypes
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
@@ -37,11 +33,13 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var sEntitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
|
||||
IEntity sDestructibleEntity;
|
||||
IDamageableComponent sDamageableComponent = null;
|
||||
IEntity sDestructibleEntity = null; ;
|
||||
DamageableComponent sDamageableComponent = null;
|
||||
DestructibleComponent sDestructibleComponent = null;
|
||||
TestThresholdListenerComponent sThresholdListenerComponent = null;
|
||||
TestDestructibleListenerSystem sTestThresholdListenerSystem = null;
|
||||
DamageableSystem sDamageableSystem = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
@@ -50,34 +48,35 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<DamageableComponent>();
|
||||
sDestructibleComponent = sDestructibleEntity.GetComponent<DestructibleComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
sTestThresholdListenerSystem = sEntitySystemManager.GetEntitySystem<TestDestructibleListenerSystem>();
|
||||
sDamageableSystem = sEntitySystemManager.GetEntitySystem<DamageableSystem>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var bluntDamageType = sPrototypeManager.Index<DamageTypePrototype>("TestBlunt");
|
||||
var bluntDamage = new DamageSpecifier(sPrototypeManager.Index<DamageTypePrototype>("TestBlunt"), 10);
|
||||
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage, true);
|
||||
|
||||
// No thresholds reached yet, the earliest one is at 20 damage
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage, true);
|
||||
|
||||
// Only one threshold reached, 20
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold 20
|
||||
var msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
var msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
var threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
@@ -85,15 +84,15 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 30, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage*3, true);
|
||||
|
||||
// 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));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
// Threshold 50
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
@@ -113,50 +112,50 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.NotNull(threshold.Trigger);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Damage for 50 again, up to 100 now
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 50, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage*5, true);
|
||||
|
||||
// No thresholds reached as they weren't healed below the trigger amount
|
||||
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
|
||||
Assert.IsEmpty(sTestThresholdListenerSystem.ThresholdsReached);
|
||||
|
||||
// Set damage to 0
|
||||
sDamageableComponent.TrySetAllDamage(0);
|
||||
sDamageableSystem.SetAllDamage(sDamageableComponent, 0);
|
||||
|
||||
// Damage for 100, up to 100
|
||||
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 100, true));
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage*10, true);
|
||||
|
||||
// Two thresholds reached as damage increased past the previous, 20 and 50
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(2));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(2));
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Heal the entity for 40 damage, down to 60
|
||||
sDamageableComponent.TryChangeDamage(bluntDamageType, -40, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage*-4, true);
|
||||
|
||||
// Thresholds don't work backwards
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Damage for 10, up to 70
|
||||
sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage, true);
|
||||
|
||||
// Not enough healing to de-trigger a threshold
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Heal by 30, down to 40
|
||||
sDamageableComponent.TryChangeDamage(bluntDamageType, -30, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage*-3, true);
|
||||
|
||||
// Thresholds don't work backwards
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Damage up to 50 again
|
||||
sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage, true);
|
||||
|
||||
// The 50 threshold should have triggered again, after being healed
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
@@ -178,22 +177,22 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
// Reset thresholds reached
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Heal all damage
|
||||
sDamageableComponent.TrySetAllDamage(0);
|
||||
sDamageableSystem.SetAllDamage(sDamageableComponent, 0);
|
||||
|
||||
// Damage up to 50
|
||||
sDamageableComponent.TryChangeDamage(bluntDamageType, 50, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage*5, 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);
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Has.Exactly(2).Items);
|
||||
|
||||
// Verify the first one, should be the lowest one (20)
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
msg = sTestThresholdListenerSystem.ThresholdsReached[0];
|
||||
var trigger = (DamageTrigger) msg.Threshold.Trigger;
|
||||
Assert.NotNull(trigger);
|
||||
Assert.That(trigger.Damage, Is.EqualTo(20));
|
||||
@@ -204,7 +203,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(threshold.Behaviors, Is.Empty);
|
||||
|
||||
// Verify the second one, should be the highest one (50)
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[1];
|
||||
msg = sTestThresholdListenerSystem.ThresholdsReached[1];
|
||||
trigger = (DamageTrigger) msg.Threshold.Trigger;
|
||||
Assert.NotNull(trigger);
|
||||
Assert.That(trigger.Damage, Is.EqualTo(50));
|
||||
@@ -229,10 +228,10 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
// Reset thresholds reached
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
sTestThresholdListenerSystem.ThresholdsReached.Clear();
|
||||
|
||||
// Heal the entity completely
|
||||
sDamageableComponent.TrySetAllDamage(0);
|
||||
sDamageableSystem.SetAllDamage(sDamageableComponent, 0);
|
||||
|
||||
// Check that the entity has 0 damage
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
@@ -245,13 +244,13 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
}
|
||||
|
||||
// Damage the entity up to 50 damage again
|
||||
sDamageableComponent.TryChangeDamage(bluntDamageType, 50, true);
|
||||
sDamageableSystem.TryChangeDamage(sDestructibleEntity.Uid, bluntDamage*5, 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);
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Set both thresholds to trigger multiple times
|
||||
foreach (var destructibleThreshold in sDestructibleComponent.Thresholds)
|
||||
@@ -264,7 +263,7 @@ namespace Content.IntegrationTests.Tests.Destructible
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
// They shouldn't have been triggered by changing TriggersOnce
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
Assert.That(sTestThresholdListenerSystem.ThresholdsReached, Is.Empty);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using Content.Server.Destructible;
|
||||
using Robust.Shared.GameObjects;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
/// <summary>
|
||||
/// This is just a system for testing destructible thresholds. Whenever any threshold is reached, this will add that
|
||||
/// threshold to a list for checking during testing.
|
||||
/// </summary>
|
||||
public class TestDestructibleListenerSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DestructibleComponent, DamageThresholdReached>(AddThresholdsToList);
|
||||
}
|
||||
|
||||
public void AddThresholdsToList(EntityUid _, DestructibleComponent comp, DamageThresholdReached args)
|
||||
{
|
||||
ThresholdsReached.Add(args);
|
||||
}
|
||||
|
||||
public List<DamageThresholdReached> ThresholdsReached = new();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Destructible;
|
||||
using Robust.Shared.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Disposal.Tube.Components;
|
||||
using Content.Server.Disposal.Unit.Components;
|
||||
using Content.Server.Disposal.Unit.EntitySystems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Coordinates;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Disposal
|
||||
{
|
||||
@@ -19,13 +20,13 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
[TestOf(typeof(DisposalUnitComponent))]
|
||||
public class DisposalUnitTest : ContentIntegrationTest
|
||||
{
|
||||
private void UnitInsert(DisposalUnitComponent unit, bool result, params IEntity[] entities)
|
||||
private async Task UnitInsert(DisposalUnitComponent unit, bool result, params IEntity[] entities)
|
||||
{
|
||||
List<Task> insertionTasks = new();
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
var insertTask = unit.TryInsert(entity);
|
||||
Assert.That(EntitySystem.Get<DisposalUnitSystem>().CanInsert(unit, entity), Is.EqualTo(result));
|
||||
insertTask.ContinueWith(task =>
|
||||
var insertTask = unit.TryInsert(entity).ContinueWith(task =>
|
||||
{
|
||||
Assert.That(task.Result, Is.EqualTo(result));
|
||||
if (result)
|
||||
@@ -34,7 +35,9 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
Assert.That(entity.Transform.Parent, Is.EqualTo(unit.Owner.Transform));
|
||||
}
|
||||
});
|
||||
insertionTasks.Add(insertTask);
|
||||
}
|
||||
Task.WaitAll(insertionTasks.ToArray());
|
||||
}
|
||||
|
||||
private void UnitContains(DisposalUnitComponent unit, bool result, params IEntity[] entities)
|
||||
@@ -45,9 +48,9 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
}
|
||||
}
|
||||
|
||||
private void UnitInsertContains(DisposalUnitComponent unit, bool result, params IEntity[] entities)
|
||||
private async void UnitInsertContains(DisposalUnitComponent unit, bool result, params IEntity[] entities)
|
||||
{
|
||||
UnitInsert(unit, result, entities);
|
||||
await UnitInsert(unit, result, entities);
|
||||
UnitContains(unit, result, entities);
|
||||
}
|
||||
|
||||
@@ -68,7 +71,9 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
- type: Body
|
||||
- type: MobState
|
||||
- type: Damageable
|
||||
damagePrototype: biologicalDamageContainer
|
||||
damageContainer: Biological
|
||||
- type: Physics
|
||||
bodyType: KinematicController
|
||||
|
||||
- type: entity
|
||||
name: WrenchDummy
|
||||
@@ -78,6 +83,8 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
- type: Tool
|
||||
qualities:
|
||||
- Anchoring
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
|
||||
- type: entity
|
||||
name: DisposalUnitDummy
|
||||
@@ -94,44 +101,78 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
id: DisposalTrunkDummy
|
||||
components:
|
||||
- type: DisposalEntry
|
||||
- type: Transform
|
||||
anchored: true
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
var options = new ServerIntegrationOptions{ExtraPrototypes = Prototypes};
|
||||
var options = new ServerIntegrationOptions { ExtraPrototypes = Prototypes };
|
||||
var server = StartServerDummyTicker(options);
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
IEntity human;
|
||||
IEntity wrench;
|
||||
DisposalUnitComponent unit;
|
||||
IEntity human = default!;
|
||||
IEntity wrench = default!;
|
||||
IEntity disposalUnit = default!;
|
||||
IEntity disposalTrunk = default!;
|
||||
DisposalUnitComponent unit = default!;
|
||||
EntityCoordinates coordinates = default!;
|
||||
|
||||
server.Assert(async () =>
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var pauseManager = server.ResolveDependency<IPauseManager>();
|
||||
var componentFactory = server.ResolveDependency<IComponentFactory>();
|
||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
|
||||
// Build up test environment
|
||||
server.Post(() =>
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
// Create a one tile grid to anchor our disposal unit to.
|
||||
var mapId = mapManager.CreateMap();
|
||||
|
||||
mapManager.CreateNewMapEntity(MapId.Nullspace);
|
||||
pauseManager.AddUninitializedMap(mapId);
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var gridId = new GridId(1);
|
||||
|
||||
if (!mapManager.TryGetGrid(gridId, out var grid))
|
||||
{
|
||||
grid = mapManager.CreateGrid(mapId, gridId);
|
||||
}
|
||||
|
||||
var tileDefinition = tileDefinitionManager["underplating"];
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
coordinates = grid.ToCoordinates();
|
||||
|
||||
grid.SetTile(coordinates, tile);
|
||||
|
||||
pauseManager.DoMapInitialize(mapId);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Spawn the entities
|
||||
human = entityManager.SpawnEntity("HumanDummy", MapCoordinates.Nullspace);
|
||||
wrench = entityManager.SpawnEntity("WrenchDummy", MapCoordinates.Nullspace);
|
||||
var disposalUnit = entityManager.SpawnEntity("DisposalUnitDummy", MapCoordinates.Nullspace);
|
||||
var disposalTrunk = entityManager.SpawnEntity("DisposalTrunkDummy", disposalUnit.Transform.MapPosition);
|
||||
human = entityManager.SpawnEntity("HumanDummy", coordinates);
|
||||
wrench = entityManager.SpawnEntity("WrenchDummy", coordinates);
|
||||
disposalUnit = entityManager.SpawnEntity("DisposalUnitDummy", coordinates);
|
||||
disposalTrunk = entityManager.SpawnEntity("DisposalTrunkDummy", disposalUnit.Transform.MapPosition);
|
||||
|
||||
// Check that we have a grid, so that we can anchor our unit
|
||||
Assert.That(mapManager.TryFindGridAt(disposalUnit.Transform.MapPosition, out var _));
|
||||
|
||||
// Test for components existing
|
||||
Assert.True(disposalUnit.TryGetComponent(out unit!));
|
||||
Assert.True(disposalTrunk.HasComponent<DisposalEntryComponent>());
|
||||
|
||||
// Can't insert, unanchored and unpowered
|
||||
var physics = disposalUnit.GetComponent<IPhysBody>();
|
||||
physics.BodyType = BodyType.Dynamic;
|
||||
Assert.False(unit.Owner.Transform.Anchored);
|
||||
unit.Owner.Transform.Anchored = false;
|
||||
UnitInsertContains(unit, false, human, wrench, disposalUnit, disposalTrunk);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Anchor the disposal unit
|
||||
physics.BodyType = BodyType.Static;
|
||||
unit.Owner.Transform.Anchored = true;
|
||||
|
||||
// No power
|
||||
Assert.False(unit.Powered);
|
||||
@@ -141,19 +182,28 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
|
||||
// Can insert mobs and items
|
||||
UnitInsertContains(unit, true, human, wrench);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Move the disposal trunk away
|
||||
disposalTrunk.Transform.WorldPosition += (1, 0);
|
||||
|
||||
// Fail to flush with a mob and an item
|
||||
Flush(unit, false, human, wrench);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Move the disposal trunk back
|
||||
disposalTrunk.Transform.WorldPosition -= (1, 0);
|
||||
|
||||
// Fail to flush with a mob and an item, no power
|
||||
Flush(unit, false, human, wrench);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Remove power need
|
||||
Assert.True(disposalUnit.TryGetComponent(out ApcPowerReceiverComponent? power));
|
||||
power!.NeedsPower = false;
|
||||
@@ -161,12 +211,13 @@ namespace Content.IntegrationTests.Tests.Disposal
|
||||
|
||||
// Flush with a mob and an item
|
||||
Flush(unit, true, human, wrench);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Re-pressurizing
|
||||
Flush(unit, false);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||
}
|
||||
|
||||
// Just went with max health
|
||||
return meleeWeaponComponent.Damage / 300.0f;
|
||||
return meleeWeaponComponent.Damage.Total / 300.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
@@ -10,7 +10,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || target.Deleted || !target.TryGetComponent(out IDamageableComponent? damageableComponent))
|
||||
if (target == null || target.Deleted || !target.TryGetComponent(out DamageableComponent? damageableComponent))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -32,7 +32,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent<IDamageableComponent>())
|
||||
if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent<DamageableComponent>())
|
||||
{
|
||||
result.Add(player.AttachedEntity);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,8 @@ using Content.Server.Pressure;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
@@ -22,21 +19,14 @@ namespace Content.Server.Atmos.Components
|
||||
{
|
||||
public override string Name => "Barotrauma";
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")] private readonly string _damageTypeID = "Blunt";
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(float airPressure)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? damageable)) return;
|
||||
if (!Owner.HasComponent<DamageableComponent>()) return;
|
||||
|
||||
var status = Owner.GetComponentOrNull<ServerAlertsComponent>();
|
||||
var highPressureMultiplier = 1f;
|
||||
@@ -59,7 +49,7 @@ namespace Content.Server.Atmos.Components
|
||||
goto default;
|
||||
|
||||
// Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
|
||||
damageable.TryChangeDamage(DamageType, Atmospherics.LowPressureDamage,true);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, Damage * Atmospherics.LowPressureDamage, true);
|
||||
|
||||
if (status == null) break;
|
||||
|
||||
@@ -79,10 +69,10 @@ namespace Content.Server.Atmos.Components
|
||||
if(pressure < Atmospherics.WarningHighPressure)
|
||||
goto default;
|
||||
|
||||
var damage = (int) MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage);
|
||||
var damageScale = (int) MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage);
|
||||
|
||||
// Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
|
||||
damageable.TryChangeDamage(DamageType, damage,true);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, Damage * damageScale, true);
|
||||
|
||||
if (status == null) break;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Temperature;
|
||||
@@ -20,8 +19,6 @@ using Robust.Shared.Localization;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
@@ -45,17 +42,9 @@ namespace Content.Server.Atmos.Components
|
||||
[DataField("canResistFire")]
|
||||
public bool CanResistFire { get; private set; } = false;
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Heat"!;
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
public void Extinguish()
|
||||
{
|
||||
@@ -102,12 +91,9 @@ namespace Content.Server.Atmos.Components
|
||||
temp.ReceiveHeat(200 * FireStacks);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
// TODO ATMOS Fire resistance from armor
|
||||
var damage = Math.Min((int) (FireStacks * 2.5f), 10);
|
||||
damageable.TryChangeDamage(DamageType, damage, false);
|
||||
}
|
||||
var damageScale = Math.Min((int) (FireStacks * 2.5f), 10);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, Damage * damageScale);
|
||||
|
||||
AdjustFireStacks(-0.1f * (_resisting ? 10f : 1f));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Body.Circulatory;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Mechanism;
|
||||
|
||||
@@ -12,9 +12,6 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -92,21 +89,13 @@ namespace Content.Server.Body.Respiratory
|
||||
|
||||
[ViewVariables] public bool Suffocating { get; private set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("suffocationDamage")] private int _damage = 1;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("suffocationDamageRecovery")] private int _damageRecovery = 1;
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Asphyxiation"!;
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField("damageRecovery", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier DamageRecovery = default!;
|
||||
|
||||
private Dictionary<Gas, float> NeedsAndDeficit(float frameTime)
|
||||
{
|
||||
@@ -358,27 +347,19 @@ namespace Content.Server.Body.Respiratory
|
||||
alertsComponent.ShowAlert(AlertType.LowOxygen);
|
||||
}
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
damageable.TryChangeDamage(DamageType, _damage, false);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, Damage);
|
||||
}
|
||||
|
||||
private void StopSuffocation()
|
||||
{
|
||||
Suffocating = false;
|
||||
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
damageable.TryChangeDamage(DamageType, -_damageRecovery, false);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerAlertsComponent? alertsComponent))
|
||||
{
|
||||
alertsComponent.ClearAlert(AlertType.LowOxygen);
|
||||
}
|
||||
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, DamageRecovery);
|
||||
}
|
||||
|
||||
public GasMixture Clean(BloodstreamComponent bloodstream)
|
||||
|
||||
@@ -2,13 +2,13 @@ using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Alert;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.MobState.States;
|
||||
using Content.Server.Pulling;
|
||||
using Content.Server.Stunnable.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
@@ -8,7 +8,7 @@ using Content.Server.Items;
|
||||
using Content.Server.Notification;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Notification;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Robust.Server.Player;
|
||||
@@ -30,13 +30,14 @@ namespace Content.Server.Chat.Commands
|
||||
|
||||
public string Help => Loc.GetString("suicide-command-help-text");
|
||||
|
||||
private void DealDamage(ISuicideAct suicide, IChatManager chat, IDamageableComponent damageableComponent, IEntity source, IEntity target)
|
||||
private void DealDamage(ISuicideAct suicide, IChatManager chat, IEntity target)
|
||||
{
|
||||
var kind = suicide.Suicide(target, chat);
|
||||
if (kind != SuicideKind.Special)
|
||||
{
|
||||
// TODO SUICIDE ..heh.. anyway, someone should fix this mess.
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
damageableComponent.TrySetDamage(kind switch
|
||||
DamageSpecifier damage = new(kind switch
|
||||
{
|
||||
SuicideKind.Blunt => prototypeManager.Index<DamageTypePrototype>("Blunt"),
|
||||
SuicideKind.Slash => prototypeManager.Index<DamageTypePrototype>("Slash"),
|
||||
@@ -51,6 +52,7 @@ namespace Content.Server.Chat.Commands
|
||||
_ => prototypeManager.Index<DamageTypePrototype>("Blunt")
|
||||
},
|
||||
200);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(target.Uid, damage, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +79,6 @@ namespace Content.Server.Chat.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var dmgComponent = owner.GetComponent<IDamageableComponent>();
|
||||
//TODO: needs to check if the mob is actually alive
|
||||
//TODO: maybe set a suicided flag to prevent resurrection?
|
||||
|
||||
@@ -90,7 +91,7 @@ namespace Content.Server.Chat.Commands
|
||||
|
||||
if (suicide != null)
|
||||
{
|
||||
DealDamage(suicide, chat, dmgComponent, itemComponent.Owner, owner);
|
||||
DealDamage(suicide, chat, owner);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ namespace Content.Server.Chat.Commands
|
||||
var suicide = entity.GetAllComponents<ISuicideAct>().FirstOrDefault();
|
||||
if (suicide != null)
|
||||
{
|
||||
DealDamage(suicide, chat, dmgComponent, entity, owner);
|
||||
DealDamage(suicide, chat, owner);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +120,8 @@ namespace Content.Server.Chat.Commands
|
||||
var selfMessage = Loc.GetString("suicide-command-default-text-self");
|
||||
owner.PopupMessage(selfMessage);
|
||||
|
||||
dmgComponent.TrySetDamage(IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("Piercing"), 200);
|
||||
DamageSpecifier damage = new(IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("Bloodloss"), 200);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(owner.Uid, damage, true);
|
||||
|
||||
// Prevent the player from returning to the body.
|
||||
// Note that mind cannot be null because otherwise owner would be null.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Interaction.Components;
|
||||
using Content.Server.MobState.States;
|
||||
using Content.Server.Weapon.Melee;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
@@ -3,66 +3,23 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects
|
||||
{
|
||||
/// <summary>
|
||||
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
|
||||
/// and to update its damage values.
|
||||
/// Default metabolism for medicine reagents.
|
||||
/// </summary>
|
||||
public class HealthChange : ReagentEffect, ISerializationHooks
|
||||
public class HealthChange : ReagentEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// How much damage is changed when 1u of the reagent is metabolized.
|
||||
/// Damage to apply every metabolism cycle. Damage Ignores resistances.
|
||||
/// </summary>
|
||||
[DataField("healthChange")]
|
||||
public float AmountToChange { get; set; } = 1.0f;
|
||||
[DataField("damage", required: true)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
// TODO DAMAGE UNITS When damage units support decimals, get rid of this.
|
||||
// See also _accumulatedDamage in ThirstComponent and HungerComponent
|
||||
private float _accumulatedDamage;
|
||||
|
||||
/// <summary>
|
||||
/// Damage group to change.
|
||||
/// </summary>
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove ISerializationHooks, if no longer needed.
|
||||
[DataField("damageGroup", required: true)]
|
||||
private readonly string _damageGroupID = default!;
|
||||
public DamageGroupPrototype DamageGroup = default!;
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
DamageGroup = IoCManager.Resolve<IPrototypeManager>().Index<DamageGroupPrototype>(_damageGroupID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes damage if a DamageableComponent can be found.
|
||||
/// </summary>
|
||||
public override void Metabolize(IEntity solutionEntity, Solution.ReagentQuantity amount)
|
||||
{
|
||||
if (solutionEntity.TryGetComponent(out IDamageableComponent? damageComponent))
|
||||
{
|
||||
damageComponent.TryChangeDamage(DamageGroup, (int)AmountToChange, true);
|
||||
|
||||
float decHealthChange = (float) (AmountToChange - (int) AmountToChange);
|
||||
_accumulatedDamage += decHealthChange;
|
||||
|
||||
if (_accumulatedDamage >= 1)
|
||||
{
|
||||
damageComponent.TryChangeDamage(DamageGroup, 1, true);
|
||||
_accumulatedDamage -= 1;
|
||||
}
|
||||
|
||||
else if(_accumulatedDamage <= -1)
|
||||
{
|
||||
damageComponent.TryChangeDamage(DamageGroup, -1, true);
|
||||
_accumulatedDamage += 1;
|
||||
}
|
||||
}
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(solutionEntity.Uid, Damage, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Text;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -42,7 +42,7 @@ namespace Content.Server.Damage.Commands
|
||||
return $"Damage Types:{msg}";
|
||||
}
|
||||
|
||||
private delegate void Damage(IDamageableComponent damageable, bool ignoreResistances);
|
||||
private delegate void Damage(IEntity entity, bool ignoreResistances);
|
||||
|
||||
private bool TryParseEntity(IConsoleShell shell, IPlayerSession? player, string arg,
|
||||
[NotNullWhen(true)] out IEntity? entity)
|
||||
@@ -85,7 +85,7 @@ namespace Content.Server.Damage.Commands
|
||||
|
||||
private bool TryParseDamageArgs(
|
||||
IConsoleShell shell,
|
||||
IPlayerSession? player,
|
||||
IEntity target,
|
||||
string[] args,
|
||||
[NotNullWhen(true)] out Damage? func)
|
||||
{
|
||||
@@ -101,23 +101,12 @@ namespace Content.Server.Damage.Commands
|
||||
|
||||
if (_prototypeManager.TryIndex<DamageGroupPrototype>(args[0], out var damageGroup))
|
||||
{
|
||||
func = (damageable, ignoreResistances) =>
|
||||
func = (entity, ignoreResistances) =>
|
||||
{
|
||||
if (!damageable.ApplicableDamageGroups.Contains(damageGroup))
|
||||
{
|
||||
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage group {damageGroup}");
|
||||
var damage = new DamageSpecifier(damageGroup, amount);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(entity.Uid, damage, ignoreResistances);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!damageable.TryChangeDamage(damageGroup, amount, ignoreResistances))
|
||||
{
|
||||
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} received no damage.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine($"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageGroup} damage{(ignoreResistances ? ", ignoring resistances." : ".")}");
|
||||
shell.WriteLine($"Damaged entity {entity.Name} with id {entity.Uid} for {amount} {damageGroup} damage{(ignoreResistances ? ", ignoring resistances." : ".")}");
|
||||
};
|
||||
|
||||
return true;
|
||||
@@ -125,23 +114,12 @@ namespace Content.Server.Damage.Commands
|
||||
// Fall back to DamageType
|
||||
else if (_prototypeManager.TryIndex<DamageTypePrototype>(args[0], out var damageType))
|
||||
{
|
||||
func = (damageable, ignoreResistances) =>
|
||||
func = (entity, ignoreResistances) =>
|
||||
{
|
||||
if (!damageable.IsSupportedDamageType(damageType))
|
||||
{
|
||||
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage type {damageType}");
|
||||
var damage = new DamageSpecifier(damageType, amount);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(entity.Uid, damage, ignoreResistances);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!damageable.TryChangeDamage(damageType, amount, ignoreResistances))
|
||||
{
|
||||
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} received no damage.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine($"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageType} damage{(ignoreResistances ? ", ignoring resistances." : ".")}");
|
||||
shell.WriteLine($"Damaged entity {entity.Name} with id {entity.Uid} for {amount} {damageType} damage{(ignoreResistances ? ", ignoring resistances." : ".")}");
|
||||
|
||||
};
|
||||
return true;
|
||||
@@ -185,10 +163,6 @@ namespace Content.Server.Damage.Commands
|
||||
shell.WriteLine($"Invalid number of arguments ({args.Length}).\n{Help}");
|
||||
return;
|
||||
case var n when n >= 2 && n <= 4:
|
||||
if (!TryParseDamageArgs(shell, player, args, out damageFunc))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entityUid = n == 2 ? "_" : args[2];
|
||||
|
||||
@@ -199,6 +173,11 @@ namespace Content.Server.Damage.Commands
|
||||
|
||||
entity = parsedEntity;
|
||||
|
||||
if (!TryParseDamageArgs(shell, entity, args, out damageFunc))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == 4)
|
||||
{
|
||||
if (!bool.TryParse(args[3], out ignoreResistances))
|
||||
@@ -218,13 +197,13 @@ namespace Content.Server.Damage.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (!entity.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
shell.WriteLine($"Entity {entity.Name} with id {entity.Uid} does not have a {nameof(IDamageableComponent)}.");
|
||||
shell.WriteLine($"Entity {entity.Name} with id {entity.Uid} does not have a {nameof(DamageableComponent)}.");
|
||||
return;
|
||||
}
|
||||
|
||||
damageFunc(damageable, ignoreResistances);
|
||||
damageFunc(entity, ignoreResistances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Damage.Components
|
||||
@@ -19,8 +17,6 @@ namespace Content.Server.Damage.Components
|
||||
|
||||
[DataField("minimumSpeed")]
|
||||
public float MinimumSpeed { get; set; } = 20f;
|
||||
[DataField("baseDamage")]
|
||||
public int BaseDamage { get; set; } = 5;
|
||||
[DataField("factor")]
|
||||
public float Factor { get; set; } = 1f;
|
||||
[DataField("soundHit", required: true)]
|
||||
@@ -36,16 +32,8 @@ namespace Content.Server.Damage.Components
|
||||
|
||||
internal TimeSpan LastHit = TimeSpan.Zero;
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Blunt";
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier Damage = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,12 @@ namespace Content.Server.Damage.Components
|
||||
{
|
||||
public override string Name => "DamageOnLand";
|
||||
|
||||
[DataField("amount")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Amount = 1;
|
||||
|
||||
[DataField("ignoreResistances")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IgnoreResistances;
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")] public readonly string DamageTypeId = "Blunt";
|
||||
public bool IgnoreResistances = false;
|
||||
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
public DamageSpecifier Damage = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Tools.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Tool;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Damage.Components
|
||||
@@ -19,28 +16,16 @@ namespace Content.Server.Damage.Components
|
||||
|
||||
public override string Name => "DamageOnToolInteract";
|
||||
|
||||
[DataField("damage")]
|
||||
protected int Damage;
|
||||
|
||||
[DataField("tools")]
|
||||
private List<ToolQuality> _tools = new();
|
||||
|
||||
// TODO PROTOTYPE Replace these datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("weldingDamageType")]
|
||||
private readonly string _weldingDamageTypeID = "Heat";
|
||||
[DataField("weldingDamage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype WeldingDamageType = default!;
|
||||
[DataField("defaultDamageType")]
|
||||
private readonly string _defaultDamageTypeID = "Blunt";
|
||||
public DamageSpecifier WeldingDamage = default!;
|
||||
|
||||
[DataField("defaultDamage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DefaultDamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
WeldingDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_weldingDamageTypeID);
|
||||
DefaultDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_defaultDamageTypeID);
|
||||
}
|
||||
public DamageSpecifier DefaultDamage = default!;
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
@@ -50,30 +35,22 @@ namespace Content.Server.Damage.Components
|
||||
{
|
||||
if (tool.HasQuality(ToolQuality.Welding) && toolQuality == ToolQuality.Welding)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent(out WelderComponent? welder))
|
||||
if (eventArgs.Using.TryGetComponent(out WelderComponent? welder) && welder.WelderLit)
|
||||
{
|
||||
if (welder.WelderLit) return CallDamage(eventArgs, tool);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(eventArgs.Target.Uid, WeldingDamage);
|
||||
return true;
|
||||
}
|
||||
break; //If the tool quality is welding and its not lit or its not actually a welder that can be lit then its pointless to continue.
|
||||
}
|
||||
|
||||
if (tool.HasQuality(toolQuality)) return CallDamage(eventArgs, tool);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool)
|
||||
if (tool.HasQuality(toolQuality))
|
||||
{
|
||||
if (!eventArgs.Target.TryGetComponent<IDamageableComponent>(out var damageable))
|
||||
return false;
|
||||
|
||||
damageable.TryChangeDamage(tool.HasQuality(ToolQuality.Welding)
|
||||
? WeldingDamageType
|
||||
: DefaultDamageType,
|
||||
Damage);
|
||||
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(eventArgs.Target.Uid, DefaultDamage);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ using Content.Shared.Damage;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Damage.Components
|
||||
{
|
||||
@@ -14,21 +13,13 @@ namespace Content.Server.Damage.Components
|
||||
{
|
||||
public override string Name => "DamageOtherOnHit";
|
||||
|
||||
[DataField("amount")]
|
||||
public int Amount { get; } = 1;
|
||||
|
||||
[DataField("ignoreResistances")]
|
||||
public bool IgnoreResistances { get; } = false;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IgnoreResistances = false;
|
||||
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Blunt";
|
||||
public DamageTypePrototype DamageType { get; set; } = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ using Content.Server.Atmos.Components;
|
||||
using Content.Server.Nutrition.Components;
|
||||
using Content.Server.Nutrition.EntitySystems;
|
||||
using Content.Server.Stunnable.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -34,7 +34,7 @@ namespace Content.Server.Damage
|
||||
|
||||
if (user.TryGetComponent<ActorComponent>(out var player))
|
||||
{
|
||||
if (!target.HasComponent<IDamageableComponent>() && !target.HasComponent<HungerComponent>() &&
|
||||
if (!target.HasComponent<DamageableComponent>() && !target.HasComponent<HungerComponent>() &&
|
||||
!target.HasComponent<ThirstComponent>())
|
||||
{
|
||||
return;
|
||||
@@ -59,14 +59,9 @@ namespace Content.Server.Damage
|
||||
|
||||
public static void PerformRejuvenate(IEntity target)
|
||||
{
|
||||
if (target.TryGetComponent(out IDamageableComponent? damage))
|
||||
if (target.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
damage.TrySetAllDamage(0);
|
||||
}
|
||||
|
||||
if (target.TryGetComponent(out IMobStateComponent? mobState))
|
||||
{
|
||||
mobState.UpdateState(0);
|
||||
EntitySystem.Get<DamageableSystem>().SetAllDamage(damageable, 0);
|
||||
}
|
||||
|
||||
if (target.TryGetComponent(out HungerComponent? hunger))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Server.Stunnable.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -18,6 +18,7 @@ namespace Content.Server.Damage.Systems
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -27,7 +28,7 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void HandleCollide(EntityUid uid, DamageOnHighSpeedImpactComponent component, StartCollideEvent args)
|
||||
{
|
||||
if (!ComponentManager.TryGetComponent(uid, out IDamageableComponent? damageable)) return;
|
||||
if (!ComponentManager.HasComponent<DamageableComponent>(uid)) return;
|
||||
|
||||
var otherBody = args.OtherFixture.Body.Owner;
|
||||
var speed = args.OurFixture.Body.LinearVelocity.Length;
|
||||
@@ -41,12 +42,11 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
component.LastHit = _gameTiming.CurTime;
|
||||
|
||||
var damage = (int) (component.BaseDamage * (speed / component.MinimumSpeed) * component.Factor);
|
||||
|
||||
if (ComponentManager.TryGetComponent(uid, out StunnableComponent? stun) && _robustRandom.Prob(component.StunChance))
|
||||
stun.Stun(component.StunSeconds);
|
||||
|
||||
damageable.TryChangeDamage(component.DamageType, damage);
|
||||
var damageScale = (speed / component.MinimumSpeed) * component.Factor;
|
||||
_damageableSystem.TryChangeDamage(uid, component.Damage * damageScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Damage.Systems
|
||||
{
|
||||
public sealed class DamageOnLandSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DamageOnLandComponent, ComponentInit>(HandleInit);
|
||||
SubscribeLocalEvent<DamageOnLandComponent, LandEvent>(DamageOnLand);
|
||||
}
|
||||
|
||||
private void HandleInit(EntityUid uid, DamageOnLandComponent component, ComponentInit args)
|
||||
{
|
||||
component.DamageType = _protoManager.Index<DamageTypePrototype>(component.DamageTypeId);
|
||||
}
|
||||
|
||||
private void DamageOnLand(EntityUid uid, DamageOnLandComponent component, LandEvent args)
|
||||
{
|
||||
if (!ComponentManager.TryGetComponent<IDamageableComponent>(uid, out var damageable))
|
||||
return;
|
||||
|
||||
damageable.TryChangeDamage(component.DamageType, component.Amount, component.IgnoreResistances);
|
||||
_damageableSystem.TryChangeDamage(uid, component.Damage, component.IgnoreResistances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Damage.Systems
|
||||
{
|
||||
public class DamageOtherOnHitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<DamageOtherOnHitComponent, ThrowDoHitEvent>(OnDoHit);
|
||||
@@ -14,10 +17,7 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
|
||||
{
|
||||
if (!args.Target.TryGetComponent(out IDamageableComponent? damageable))
|
||||
return;
|
||||
|
||||
damageable.TryChangeDamage(component.DamageType, component.Amount, component.IgnoreResistances);
|
||||
_damageableSystem.TryChangeDamage(args.Target.Uid, component.Damage, component.IgnoreResistances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Damage.Systems
|
||||
{
|
||||
@@ -13,6 +12,7 @@ namespace Content.Server.Damage.Systems
|
||||
public class GodmodeSystem : EntitySystem
|
||||
{
|
||||
private readonly Dictionary<IEntity, OldEntityInformation> _entities = new();
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -40,11 +40,9 @@ namespace Content.Server.Damage.Systems
|
||||
moved.Enabled = false;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (entity.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
damageable.SupportedDamageTypes.Clear();
|
||||
damageable.FullySupportedDamageGroups.Clear();
|
||||
damageable.ApplicableDamageGroups.Clear();
|
||||
_damageableSystem.SetDamage(damageable, new DamageSpecifier());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -67,21 +65,11 @@ namespace Content.Server.Damage.Systems
|
||||
moved.Enabled = old.MovedByPressure;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (entity.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
if (old.SupportedDamageTypes != null)
|
||||
if (old.Damage != null)
|
||||
{
|
||||
damageable.SupportedDamageTypes.UnionWith(old.SupportedDamageTypes);
|
||||
}
|
||||
|
||||
if (old.SupportedDamageGroups != null)
|
||||
{
|
||||
damageable.FullySupportedDamageGroups.UnionWith(old.SupportedDamageGroups);
|
||||
}
|
||||
|
||||
if (old.ApplicableDamageGroups != null)
|
||||
{
|
||||
damageable.ApplicableDamageGroups.UnionWith(old.ApplicableDamageGroups);
|
||||
_damageableSystem.SetDamage(damageable, old.Damage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +102,9 @@ namespace Content.Server.Damage.Systems
|
||||
Entity = entity;
|
||||
MovedByPressure = entity.IsMovedByPressure();
|
||||
|
||||
if (entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (entity.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
SupportedDamageTypes = damageable.SupportedDamageTypes.ToHashSet();
|
||||
SupportedDamageGroups = damageable.FullySupportedDamageGroups.ToHashSet();
|
||||
ApplicableDamageGroups = damageable.ApplicableDamageGroups.ToHashSet();
|
||||
Damage = damageable.Damage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,11 +112,7 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
public bool MovedByPressure { get; }
|
||||
|
||||
public HashSet<DamageTypePrototype>? SupportedDamageTypes { get; }
|
||||
|
||||
public HashSet<DamageGroupPrototype>? SupportedDamageGroups { get; }
|
||||
|
||||
public HashSet<DamageGroupPrototype>? ApplicableDamageGroups { get; }
|
||||
public DamageSpecifier? Damage { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Destructible.Thresholds;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -14,50 +14,11 @@ namespace Content.Server.Destructible
|
||||
[RegisterComponent]
|
||||
public class DestructibleComponent : Component
|
||||
{
|
||||
private DestructibleSystem _destructibleSystem = default!;
|
||||
|
||||
public override string Name => "Destructible";
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("thresholds")]
|
||||
private List<Threshold> _thresholds = new();
|
||||
public List<DamageThreshold> Thresholds = new();
|
||||
|
||||
public IReadOnlyList<Threshold> Thresholds => _thresholds;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_destructibleSystem = EntitySystem.Get<DestructibleSystem>();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
{
|
||||
if (msg.Damageable.Owner != Owner)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var threshold in _thresholds)
|
||||
{
|
||||
if (threshold.Reached(msg.Damageable, _destructibleSystem))
|
||||
{
|
||||
var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold);
|
||||
SendMessage(thresholdMessage);
|
||||
|
||||
threshold.Execute(Owner, _destructibleSystem);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Acts;
|
||||
using Content.Server.Destructible.Thresholds;
|
||||
using Content.Shared.Acts;
|
||||
using Content.Shared.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -13,5 +15,44 @@ namespace Content.Server.Destructible
|
||||
[Dependency] public readonly IRobustRandom Random = default!;
|
||||
[Dependency] public readonly AudioSystem AudioSystem = default!;
|
||||
[Dependency] public readonly ActSystem ActSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DestructibleComponent, DamageChangedEvent>(Execute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any thresholds were reached. if they were, execute them.
|
||||
/// </summary>
|
||||
public void Execute(EntityUid uid, DestructibleComponent component, DamageChangedEvent args)
|
||||
{
|
||||
foreach (var threshold in component.Thresholds)
|
||||
{
|
||||
if (threshold.Reached(args.Damageable, this))
|
||||
{
|
||||
RaiseLocalEvent(uid, new DamageThresholdReached(component, threshold));
|
||||
|
||||
threshold.Execute(component.Owner, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Currently only used for destructible integration tests. Unless other uses are found for this, maybe this should just be removed and the tests redone.
|
||||
/// <summary>
|
||||
/// Event raised when a <see cref="DamageThreshold"/> is reached.
|
||||
/// </summary>
|
||||
public class DamageThresholdReached : EntityEventArgs
|
||||
{
|
||||
public readonly DestructibleComponent Parent;
|
||||
|
||||
public readonly DamageThreshold Threshold;
|
||||
|
||||
public DamageThresholdReached(DestructibleComponent parent, DamageThreshold threshold)
|
||||
{
|
||||
Parent = parent;
|
||||
Threshold = threshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Server.Destructible.Thresholds;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Destructible
|
||||
{
|
||||
public class DestructibleThresholdReachedMessage : ComponentMessage
|
||||
{
|
||||
public DestructibleThresholdReachedMessage(DestructibleComponent parent, Threshold threshold)
|
||||
{
|
||||
Parent = parent;
|
||||
Threshold = threshold;
|
||||
}
|
||||
|
||||
public DestructibleComponent Parent { get; }
|
||||
|
||||
public Threshold Threshold { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Destructible.Thresholds.Behaviors;
|
||||
using Content.Server.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -9,7 +9,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.Destructible.Thresholds
|
||||
{
|
||||
[DataDefinition]
|
||||
public class Threshold
|
||||
public class DamageThreshold
|
||||
{
|
||||
[DataField("behaviors")]
|
||||
private List<IThresholdBehavior> _behaviors = new();
|
||||
@@ -49,7 +49,7 @@ namespace Content.Server.Destructible.Thresholds
|
||||
/// </summary>
|
||||
[ViewVariables] public IReadOnlyList<IThresholdBehavior> Behaviors => _behaviors;
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
public bool Reached(DamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
if (Trigger == null)
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
[DataField("triggers")]
|
||||
public List<IThresholdTrigger> Triggers { get; set; } = new();
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
public bool Reached(DamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
foreach (var trigger in Triggers)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
{
|
||||
@@ -15,12 +14,8 @@ namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
[DataDefinition]
|
||||
public class DamageGroupTrigger : IThresholdTrigger
|
||||
{
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// While you're at it, maybe also combine damageGroup and damage into a dictionary, and allow it to test a sum
|
||||
// of damage types?
|
||||
[DataField("damageGroup", required: true)]
|
||||
private string _damageGroupID { get; set; } = default!;
|
||||
public DamageGroupPrototype DamageGroup => IoCManager.Resolve<IPrototypeManager>().Index<DamageGroupPrototype>(_damageGroupID);
|
||||
[DataField("damageGroup", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<DamageGroupPrototype>))]
|
||||
public string DamageGroup { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of damage at which this threshold will trigger.
|
||||
@@ -28,15 +23,9 @@ namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
[DataField("damage", required: true)]
|
||||
public int Damage { get; set; } = default!;
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
public bool Reached(DamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
if (DamageGroup == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return damageable.TryGetDamage(DamageGroup, out var damageReceived) &&
|
||||
damageReceived >= Damage;
|
||||
return damageable.DamagePerGroup[DamageGroup] >= Damage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
@@ -18,7 +18,7 @@ namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
[DataField("damage", required: true)]
|
||||
public int Damage { get; set; } = default!;
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
public bool Reached(DamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
return damageable.TotalDamage >= Damage;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
{
|
||||
@@ -15,24 +14,15 @@ namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
[DataDefinition]
|
||||
public class DamageTypeTrigger : IThresholdTrigger
|
||||
{
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// While you're at it, maybe also combine damageGroup and damage into a dictionary, and allow it to test a sum
|
||||
// of damage types?
|
||||
[DataField("damageType", required:true)]
|
||||
public string _damageTypeID { get; set; } = default!;
|
||||
public DamageTypePrototype DamageType => IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
[DataField("damageType", required:true, customTypeSerializer: typeof(PrototypeIdSerializer<DamageTypePrototype>))]
|
||||
public string DamageType { get; set; } = default!;
|
||||
|
||||
[DataField("damage", required: true)]
|
||||
public int Damage { get; set; } = default!;
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
public bool Reached(DamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
if (DamageType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return damageable.TryGetDamage(DamageType, out var damageReceived) &&
|
||||
return damageable.Damage.DamageDict.TryGetValue(DamageType, out var damageReceived) &&
|
||||
damageReceived >= Damage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
{
|
||||
@@ -13,6 +13,6 @@ namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
/// dependencies from, if any.
|
||||
/// </param>
|
||||
/// <returns>true if this trigger has been reached, false otherwise.</returns>
|
||||
bool Reached(IDamageableComponent damageable, DestructibleSystem system);
|
||||
bool Reached(DamageableComponent damageable, DestructibleSystem system);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Destructible.Thresholds.Triggers
|
||||
[DataField("triggers")]
|
||||
public List<IThresholdTrigger> Triggers { get; } = new();
|
||||
|
||||
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
|
||||
public bool Reached(DamageableComponent damageable, DestructibleSystem system)
|
||||
{
|
||||
foreach (var trigger in Triggers)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Players;
|
||||
@@ -40,35 +39,6 @@ namespace Content.Server.DoAfter
|
||||
return new DoAfterComponentState(toAdd);
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
if (DoAfters.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!msg.TookDamage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var doAfter in _doAfters.Keys)
|
||||
{
|
||||
if (doAfter.EventArgs.BreakOnDamage)
|
||||
{
|
||||
doAfter.TookDamage = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(DoAfter doAfter)
|
||||
{
|
||||
_doAfters.Add(doAfter, _runningIndex);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -14,6 +15,28 @@ namespace Content.Server.DoAfter
|
||||
private readonly List<DoAfter> _cancelled = new();
|
||||
private readonly List<DoAfter> _finished = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DoAfterComponent, DamageChangedEvent>(HandleDamage);
|
||||
}
|
||||
|
||||
public void HandleDamage(EntityUid _, DoAfterComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (component.DoAfters.Count == 0 || !args.DamageIncreased)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var doAfter in component.DoAfters)
|
||||
{
|
||||
if (doAfter.EventArgs.BreakOnDamage)
|
||||
{
|
||||
doAfter.TookDamage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
@@ -11,7 +11,6 @@ using Content.Server.Hands.Components;
|
||||
using Content.Server.Stunnable.Components;
|
||||
using Content.Server.Tools.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Sound;
|
||||
@@ -22,16 +21,11 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Doors.Components
|
||||
{
|
||||
@@ -47,17 +41,9 @@ namespace Content.Server.Doors.Components
|
||||
[DataField("tryOpenDoorSound")]
|
||||
private SoundSpecifier _tryOpenDoorSound = new SoundPathSpecifier("/Audio/Effects/bang.ogg");
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Blunt";
|
||||
[DataField("crushDamage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier CrushDamage = default!;
|
||||
|
||||
public override DoorState State
|
||||
{
|
||||
@@ -90,7 +76,6 @@ namespace Content.Server.Doors.Components
|
||||
private CancellationTokenSource? _stateChangeCancelTokenSource;
|
||||
private CancellationTokenSource? _autoCloseCancelTokenSource;
|
||||
|
||||
private const int DoorCrushDamage = 15;
|
||||
private const float DoorStunTime = 5f;
|
||||
|
||||
/// <summary>
|
||||
@@ -537,7 +522,7 @@ namespace Content.Server.Doors.Components
|
||||
foreach (var e in collidingentities)
|
||||
{
|
||||
if (!e.Owner.TryGetComponent(out StunnableComponent? stun)
|
||||
|| !e.Owner.TryGetComponent(out IDamageableComponent? damage))
|
||||
|| !e.Owner.HasComponent<DamageableComponent>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -550,7 +535,8 @@ namespace Content.Server.Doors.Components
|
||||
hitsomebody = true;
|
||||
CurrentlyCrushing.Add(e.Owner.Uid);
|
||||
|
||||
damage.TryChangeDamage(DamageType, DoorCrushDamage);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(e.Owner.Uid, CrushDamage);
|
||||
|
||||
stun.Paralyze(DoorStunTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Ghost.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Preferences;
|
||||
@@ -64,12 +64,10 @@ namespace Content.Server.GameTicking.Presets
|
||||
{
|
||||
canReturn = true;
|
||||
|
||||
if (playerEntity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
//todo: what if they dont breathe lol
|
||||
//cry deeply
|
||||
damageable.TrySetDamage(IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("Asphyxiation"), 200);
|
||||
}
|
||||
DamageSpecifier damage = new(IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("Asphyxiation"), 200);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(playerEntity.Uid, damage, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ using Content.Server.Traitor;
|
||||
using Content.Server.TraitorDeathMatch.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.PDA;
|
||||
@@ -28,6 +27,7 @@ using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
|
||||
namespace Content.Server.GameTicking.Presets
|
||||
{
|
||||
@@ -194,12 +194,9 @@ namespace Content.Server.GameTicking.Presets
|
||||
{
|
||||
if (mobState.IsCritical())
|
||||
{
|
||||
// TODO: This is copy/pasted from ghost code. Really, IDamageableComponent needs a method to reliably kill the target.
|
||||
if (entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
//todo: what if they dont breathe lol
|
||||
damageable.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), 100, true);
|
||||
}
|
||||
// TODO BODY SYSTEM KILL
|
||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), 100);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(entity.Uid, damage, true);
|
||||
}
|
||||
else if (!mobState.IsDead())
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
||||
|
||||
_entityManager.EventBus.SubscribeEvent<DamageChangedEventArgs>(EventSource.Local, this, OnHealthChanged);
|
||||
_entityManager.EventBus.SubscribeEvent<DamageChangedEvent>(EventSource.Local, this, OnHealthChanged);
|
||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
_entityManager.EventBus.UnsubscribeEvent<DamageChangedEventArgs>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<DamageChangedEvent>(EventSource.Local, this);
|
||||
_playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnHealthChanged(DamageChangedEventArgs message)
|
||||
private void OnHealthChanged(DamageChangedEvent _)
|
||||
{
|
||||
_runDelayedCheck();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using Content.Server.Power.Components;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Notification.Managers;
|
||||
@@ -16,7 +15,6 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Player;
|
||||
@@ -81,16 +79,13 @@ namespace Content.Server.Light.Components
|
||||
|
||||
[ViewVariables] private ContainerSlot _lightBulbContainer = default!;
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Heat";
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
_lightBulbContainer = Owner.EnsureContainer<ContainerSlot>("light_bulb");
|
||||
}
|
||||
|
||||
@@ -116,7 +111,7 @@ namespace Content.Server.Light.Components
|
||||
|
||||
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IDamageableComponent? damageableComponent))
|
||||
if (!eventArgs.User.HasComponent<DamageableComponent>())
|
||||
{
|
||||
Eject();
|
||||
return false;
|
||||
@@ -143,7 +138,7 @@ namespace Content.Server.Light.Components
|
||||
void Burn()
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("powered-light-component-burn-hand"));
|
||||
damageableComponent.TryChangeDamage(DamageType, 20);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(eventArgs.User.Uid, Damage);
|
||||
SoundSystem.Play(Filter.Pvs(Owner), _burnHandSound.GetSound(), Owner);
|
||||
}
|
||||
|
||||
@@ -285,17 +280,11 @@ namespace Content.Server.Light.Components
|
||||
case PowerChangedMessage:
|
||||
UpdateLight();
|
||||
break;
|
||||
case DamageChangedMessage msg:
|
||||
TryDestroyBulb(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryDestroyBulb(DamageChangedMessage msg)
|
||||
public void TryDestroyBulb()
|
||||
{
|
||||
if (!msg.TookDamage)
|
||||
return;
|
||||
|
||||
if (LightBulb == null || LightBulb.State == LightBulbState.Broken)
|
||||
return;
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
using System;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.MachineLinking.Events;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.MachineLinking.Events;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// System for the PoweredLightComponent. Currently bare-bones, to handle events from the DamageableSystem
|
||||
/// </summary>
|
||||
public class PoweredLightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
@@ -21,6 +23,20 @@ namespace Content.Server.Light.EntitySystems
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
|
||||
SubscribeLocalEvent<PoweredLightComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the light bulb if the light took any damage.
|
||||
/// </summary>
|
||||
public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args)
|
||||
{
|
||||
// Was it being repaired, or did it take damage?
|
||||
if (args.DamageIncreased)
|
||||
{
|
||||
// Eventually, this logic should all be done by this (or some other) system, not a component.
|
||||
component.TryDestroyBulb();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args)
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Shared.Stacks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Medical.Components
|
||||
@@ -20,12 +16,9 @@ namespace Content.Server.Medical.Components
|
||||
{
|
||||
public override string Name => "Healing";
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// This also requires changing the dictionary type, and removing a _prototypeManager.Index() call.
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[DataField("heal", required: true )]
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<string, int> Heal = new();
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
@@ -34,7 +27,7 @@ namespace Content.Server.Medical.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eventArgs.Target.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (!eventArgs.Target.HasComponent<DamageableComponent>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -55,10 +48,7 @@ namespace Content.Server.Medical.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var (damageTypeID, amount) in Heal)
|
||||
{
|
||||
damageable.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(damageTypeID), -amount, true);
|
||||
}
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(eventArgs.Target.Uid, Damage, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Acts;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.MedicalScanner;
|
||||
@@ -99,8 +98,7 @@ namespace Content.Server.Medical.Components
|
||||
private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState =
|
||||
new(
|
||||
null,
|
||||
new Dictionary<string, int>(),
|
||||
new Dictionary<string, int>(),
|
||||
null,
|
||||
false);
|
||||
|
||||
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
|
||||
@@ -116,18 +114,14 @@ namespace Content.Server.Medical.Components
|
||||
return EmptyUIState;
|
||||
}
|
||||
|
||||
if (!body.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (!body.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
return EmptyUIState;
|
||||
}
|
||||
|
||||
// Get dictionaries of damage, by fully supported damage groups and types
|
||||
var groups = new Dictionary<string, int>(damageable.GetDamagePerFullySupportedGroupIDs);
|
||||
var types = new Dictionary<string, int>(damageable.GetDamagePerTypeIDs);
|
||||
|
||||
if (_bodyContainer.ContainedEntity?.Uid == null)
|
||||
{
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, groups, types, true);
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, damageable, true);
|
||||
}
|
||||
|
||||
var cloningSystem = EntitySystem.Get<CloningSystem>();
|
||||
@@ -135,7 +129,7 @@ namespace Content.Server.Medical.Components
|
||||
mindComponent.Mind != null &&
|
||||
cloningSystem.HasDnaScan(mindComponent.Mind);
|
||||
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, groups, types, scanned);
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, damageable, scanned);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mining;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -24,16 +23,9 @@ namespace Content.Server.Mining.Components
|
||||
public override string Name => "AsteroidRock";
|
||||
private static readonly string[] SpriteStates = {"0", "1", "2", "3", "4"};
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Blunt"!;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(AsteroidRockVisuals.State, _random.Pick(SpriteStates));
|
||||
@@ -46,7 +38,7 @@ namespace Content.Server.Mining.Components
|
||||
if (!item.TryGetComponent(out MeleeWeaponComponent? meleeWeaponComponent))
|
||||
return false;
|
||||
|
||||
Owner.GetComponent<IDamageableComponent>().TryChangeDamage(DamageType, meleeWeaponComponent.Damage);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, meleeWeaponComponent.Damage);
|
||||
|
||||
if (!item.TryGetComponent(out PickaxeComponent? pickaxeComponent))
|
||||
return true;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.MobState.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.MobState.States
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMobStateComponent))]
|
||||
[ComponentReference(typeof(IMobStateComponent))]
|
||||
public class MobStateComponent : SharedMobStateComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Server.Alert;
|
||||
using Content.Server.Alert;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.MobState.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -13,7 +13,7 @@ namespace Content.Server.MobState.States
|
||||
{
|
||||
base.UpdateState(entity, threshold);
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
if (!entity.TryGetComponent(out DamageableComponent? damageable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using Content.Server.Alert;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
@@ -14,7 +13,6 @@ using Robust.Shared.Players;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Nutrition.Components
|
||||
{
|
||||
@@ -23,9 +21,7 @@ namespace Content.Server.Nutrition.Components
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
// TODO DAMAGE UNITS When damage units support decimals, get rid of this.
|
||||
// See also _accumulatedDamage in ThirstComponent and HealthChange.
|
||||
private float _accumulatedDamage;
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
// Base stuff
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -78,17 +74,9 @@ namespace Content.Server.Nutrition.Components
|
||||
{ HungerThreshold.Starving, AlertType.Starving },
|
||||
};
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Blunt"!;
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
public void HungerThresholdEffect(bool force = false)
|
||||
{
|
||||
@@ -196,21 +184,17 @@ namespace Content.Server.Nutrition.Components
|
||||
return;
|
||||
// --> Current Hunger is below dead threshold
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IMobStateComponent? mobState))
|
||||
return;
|
||||
|
||||
if (!mobState.IsDead())
|
||||
{
|
||||
// --> But they are not dead yet.
|
||||
var damage = 2 * frametime;
|
||||
_accumulatedDamage += damage - ((int) damage);
|
||||
damageable.TryChangeDamage(DamageType, (int) damage);
|
||||
if (_accumulatedDamage >= 1) {
|
||||
_accumulatedDamage -= 1;
|
||||
damageable.TryChangeDamage(DamageType, 1, true);
|
||||
_accumulatedFrameTime += frametime;
|
||||
if (_accumulatedFrameTime >= 1)
|
||||
{
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, Damage * (int) _accumulatedFrameTime, true);
|
||||
_accumulatedFrameTime -= (int) _accumulatedFrameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using Content.Server.Alert;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.MobState;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
@@ -14,7 +13,6 @@ using Robust.Shared.Players;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Nutrition.Components
|
||||
{
|
||||
@@ -23,9 +21,7 @@ namespace Content.Server.Nutrition.Components
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
// TODO DAMAGE UNITS When damage units support decimals, get rid of this.
|
||||
// See also _accumulatedDamage in HungerComponent and HealthChange.
|
||||
private float _accumulatedDamage;
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
// Base stuff
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -77,17 +73,9 @@ namespace Content.Server.Nutrition.Components
|
||||
{ThirstThreshold.Parched, AlertType.Parched},
|
||||
};
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Blunt";
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
public void ThirstThresholdEffect(bool force = false)
|
||||
{
|
||||
@@ -193,22 +181,17 @@ namespace Content.Server.Nutrition.Components
|
||||
return;
|
||||
// --> Current Hunger is below dead threshold
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IMobStateComponent? mobState))
|
||||
return;
|
||||
|
||||
if (!mobState.IsDead())
|
||||
{
|
||||
// --> But they are not dead yet.
|
||||
var damage = 2 * frametime;
|
||||
_accumulatedDamage += damage - ((int) damage);
|
||||
damageable.TryChangeDamage(DamageType, (int) damage);
|
||||
if (_accumulatedDamage >= 1)
|
||||
_accumulatedFrameTime += frametime;
|
||||
if (_accumulatedFrameTime >= 1)
|
||||
{
|
||||
_accumulatedDamage -= 1;
|
||||
damageable.TryChangeDamage(DamageType, 1, true);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, Damage * (int) _accumulatedFrameTime, true);
|
||||
_accumulatedFrameTime -= (int) _accumulatedFrameTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -27,13 +26,14 @@ namespace Content.Server.Projectiles.Components
|
||||
public override string Name => "Hitscan";
|
||||
public CollisionGroup CollisionMask => (CollisionGroup) _collisionMask;
|
||||
|
||||
|
||||
[DataField("layers")] //todo WithFormat.Flags<CollisionLayer>()
|
||||
private int _collisionMask = (int) CollisionGroup.Opaque;
|
||||
[DataField("damage")]
|
||||
public float Damage { get; set; } = 10f;
|
||||
public float MaxLength => 20.0f;
|
||||
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
public float MaxLength => 20.0f;
|
||||
private TimeSpan _startTime;
|
||||
private TimeSpan _deathTime;
|
||||
|
||||
@@ -47,19 +47,6 @@ namespace Content.Server.Projectiles.Components
|
||||
[DataField("soundHitWall")]
|
||||
private SoundSpecifier _soundHitWall = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg");
|
||||
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Piercing";
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
|
||||
public void FireEffects(IEntity user, float distance, Angle angle, IEntity? hitEntity = null)
|
||||
{
|
||||
var effectSystem = EntitySystem.Get<EffectSystem>();
|
||||
|
||||
@@ -13,12 +13,9 @@ namespace Content.Server.Projectiles.Components
|
||||
[ComponentReference(typeof(SharedProjectileComponent))]
|
||||
public class ProjectileComponent : SharedProjectileComponent
|
||||
{
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// This also requires changing the dictionary type and modifying ProjectileSystem.cs, which uses it.
|
||||
// While thats being done, also replace "damages" -> "damageTypes" For consistency.
|
||||
[DataField("damages")]
|
||||
[DataField("damage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<string, int> Damages { get; set; } = new();
|
||||
public DamageSpecifier Damage = default!;
|
||||
|
||||
[DataField("deleteOnCollide")]
|
||||
public bool DeleteOnCollide { get; } = true;
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
using Content.Server.Camera;
|
||||
using Content.Server.Projectiles.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Projectiles
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class ProjectileSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -50,16 +48,12 @@ namespace Content.Server.Projectiles
|
||||
SoundSystem.Play(playerFilter, soundHit, coordinates);
|
||||
}
|
||||
|
||||
if (!otherEntity.Deleted && otherEntity.TryGetComponent(out IDamageableComponent? damage))
|
||||
if (!otherEntity.Deleted)
|
||||
{
|
||||
EntityManager.TryGetEntity(component.Shooter, out var shooter);
|
||||
|
||||
foreach (var (damageTypeID, amount) in component.Damages)
|
||||
{
|
||||
damage.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(damageTypeID), amount);
|
||||
}
|
||||
|
||||
_damageableSystem.TryChangeDamage(otherEntity.Uid, component.Damage);
|
||||
component.DamagedEntity = true;
|
||||
// "DamagedEntity" is misleading. Hit entity may be more accurate, as the damage may have been resisted
|
||||
// by resistance sets.
|
||||
}
|
||||
|
||||
// Damaging it can delete it
|
||||
|
||||
@@ -1,50 +1,18 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Tools.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Tool;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Repairable
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class RepairableComponent : Component, IInteractUsing
|
||||
public class RepairableComponent : Component
|
||||
{
|
||||
public override string Name => "Repairable";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("fuelCost")]
|
||||
private int _fuelCost = 5;
|
||||
public int FuelCost = 5;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("doAfterDelay")]
|
||||
private int _doAfterDelay = 1;
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
// Only repair if you are using a lit welder
|
||||
if (!eventArgs.Using.TryGetComponent(out WelderComponent? welder) || !welder.WelderLit)
|
||||
return false;
|
||||
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
// Repair the target if it is damaged, oherwise do nothing
|
||||
if (damageable.TotalDamage > 0)
|
||||
{
|
||||
if (!await welder.UseTool(eventArgs.User, Owner, _doAfterDelay, ToolQuality.Welding, _fuelCost))
|
||||
return false;
|
||||
damageable.TrySetAllDamage(0);
|
||||
|
||||
Owner.PopupMessage(eventArgs.User,
|
||||
Loc.GetString("comp-repairable-repair",
|
||||
("target", Owner),
|
||||
("welder", eventArgs.Using)));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public int DoAfterDelay = 1;
|
||||
}
|
||||
}
|
||||
|
||||
46
Content.Server/Repairable/RepairableSystem.cs
Normal file
46
Content.Server/Repairable/RepairableSystem.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Content.Server.Tools.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Tool;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.Repairable
|
||||
{
|
||||
public class ReairableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<RepairableComponent, InteractUsingEvent>(Repair);
|
||||
}
|
||||
|
||||
public async void Repair(EntityUid uid, RepairableComponent component, InteractUsingEvent args)
|
||||
{
|
||||
// Only repair if you are using a lit welder
|
||||
if (!args.Used.TryGetComponent(out WelderComponent? welder) || !welder.WelderLit)
|
||||
return;
|
||||
|
||||
// Only try repair the target if it is damaged
|
||||
if (!component.Owner.TryGetComponent(out DamageableComponent? damageable) || damageable.TotalDamage == 0)
|
||||
return;
|
||||
|
||||
// Can the welder actually repair this, does it have enough fuel?
|
||||
if (!await welder.UseTool(args.User, component.Owner, component.DoAfterDelay, ToolQuality.Welding, component.FuelCost))
|
||||
return;
|
||||
|
||||
// Repair all damage
|
||||
_damageableSystem.SetAllDamage(damageable, 0);
|
||||
|
||||
component.Owner.PopupMessage(args.User,
|
||||
Loc.GetString("comp-repairable-repair",
|
||||
("target", component.Owner),
|
||||
("welder", args.Used)));
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using Content.Server.Alert;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -51,22 +50,13 @@ namespace Content.Server.Temperature.Components
|
||||
}
|
||||
}
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("coldDamageType")]
|
||||
private readonly string _coldDamageTypeID = "Cold";
|
||||
[DataField("coldDamage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype ColdDamageType = default!;
|
||||
[DataField("hotDamageType")]
|
||||
private readonly string _hotDamageTypeID = "Heat";
|
||||
public DamageSpecifier ColdDamage = default!;
|
||||
|
||||
[DataField("heatDamage", required: true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype HotDamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
ColdDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_coldDamageTypeID);
|
||||
HotDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_hotDamageTypeID);
|
||||
}
|
||||
public DamageSpecifier HeatDamage = default!;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
@@ -112,19 +102,18 @@ namespace Content.Server.Temperature.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? component)) return;
|
||||
if (!Owner.HasComponent<DamageableComponent>()) return;
|
||||
|
||||
if (CurrentTemperature >= _heatDamageThreshold)
|
||||
{
|
||||
int tempDamage = (int) Math.Floor((CurrentTemperature - _heatDamageThreshold) * _tempDamageCoefficient);
|
||||
component.TryChangeDamage(HotDamageType, tempDamage, false);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, HeatDamage * tempDamage);
|
||||
}
|
||||
else if (CurrentTemperature <= _coldDamageThreshold)
|
||||
{
|
||||
int tempDamage = (int) Math.Floor((_coldDamageThreshold - CurrentTemperature) * _tempDamageCoefficient);
|
||||
component.TryChangeDamage(ColdDamageType, tempDamage, false);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, ColdDamage * tempDamage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,8 +4,6 @@ using Content.Shared.Sound;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Weapon.Melee.Components
|
||||
{
|
||||
@@ -46,10 +44,6 @@ namespace Content.Server.Weapon.Melee.Components
|
||||
[DataField("range")]
|
||||
public float Range { get; set; } = 1;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("damage")]
|
||||
public int Damage { get; set; } = 5;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("clickAttackEffect")]
|
||||
public bool ClickAttackEffect { get; set; } = true;
|
||||
@@ -57,16 +51,8 @@ namespace Content.Server.Weapon.Melee.Components
|
||||
public TimeSpan LastAttackTime;
|
||||
public TimeSpan CooldownEnd;
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// Also remove Initialize override, if no longer needed.
|
||||
[DataField("damageType")]
|
||||
private readonly string _damageTypeID = "Blunt";
|
||||
[DataField("damage", required:true)]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype DamageType = default!;
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
|
||||
}
|
||||
public DamageSpecifier Damage = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Cooldown;
|
||||
using Content.Server.Weapon.Melee.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Physics;
|
||||
@@ -25,6 +25,7 @@ namespace Content.Server.Weapon.Melee
|
||||
public sealed class MeleeWeaponSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private SolutionContainerSystem _solutionsSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -87,12 +88,7 @@ namespace Content.Server.Weapon.Melee
|
||||
{
|
||||
var targets = new[] { target };
|
||||
SendAnimation(comp.ClickArc, angle, args.User, owner, targets, comp.ClickAttackEffect, false);
|
||||
|
||||
if (target.TryGetComponent(out IDamageableComponent? damageableComponent))
|
||||
{
|
||||
damageableComponent.TryChangeDamage(comp.DamageType, comp.Damage);
|
||||
}
|
||||
|
||||
_damageableSystem.TryChangeDamage(target.Uid, comp.Damage);
|
||||
SoundSystem.Play(Filter.Pvs(owner), comp.HitSound.GetSound(), target);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +129,7 @@ namespace Content.Server.Weapon.Melee
|
||||
if (!entity.Transform.IsMapTransform || entity == args.User)
|
||||
continue;
|
||||
|
||||
if (ComponentManager.HasComponent<IDamageableComponent>(entity.Uid))
|
||||
if (ComponentManager.HasComponent<DamageableComponent>(entity.Uid))
|
||||
{
|
||||
hitEntities.Add(entity);
|
||||
}
|
||||
@@ -157,10 +153,7 @@ namespace Content.Server.Weapon.Melee
|
||||
|
||||
foreach (var entity in hitEntities)
|
||||
{
|
||||
if (entity.TryGetComponent<IDamageableComponent>(out var damageComponent))
|
||||
{
|
||||
damageComponent.TryChangeDamage(comp.DamageType, comp.Damage);
|
||||
}
|
||||
_damageableSystem.TryChangeDamage(entity.Uid, comp.Damage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Projectiles.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Sound;
|
||||
@@ -188,13 +186,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
||||
{
|
||||
if (energyRatio < 1.0)
|
||||
{
|
||||
var newDamages = new Dictionary<string, int>(projectileComponent.Damages.Count);
|
||||
foreach (var (damageType, damage) in projectileComponent.Damages)
|
||||
{
|
||||
newDamages.Add(damageType, (int) (damage * energyRatio));
|
||||
}
|
||||
|
||||
projectileComponent.Damages = newDamages;
|
||||
projectileComponent.Damage *= energyRatio;
|
||||
}
|
||||
} else if (entity.TryGetComponent(out HitscanComponent? hitscanComponent))
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ using Content.Server.Camera;
|
||||
using Content.Server.Projectiles.Components;
|
||||
using Content.Server.Weapon.Ranged.Ammunition.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Sound;
|
||||
@@ -395,13 +395,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
|
||||
var result = rayCastResults[0];
|
||||
var distance = result.Distance;
|
||||
hitscan.FireEffects(shooter, distance, angle, result.HitEntity);
|
||||
|
||||
if (!result.HitEntity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
return;
|
||||
|
||||
damageable.TryChangeDamage(hitscan.DamageType, (int)Math.Round(hitscan.Damage, MidpointRounding.AwayFromZero));
|
||||
//I used Math.Round over Convert.toInt32, as toInt32 always rounds to
|
||||
//even numbers if halfway between two numbers, rather than rounding to nearest
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(result.HitEntity.Uid, hitscan.Damage);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.CombatMode;
|
||||
using Content.Server.Hands.Components;
|
||||
@@ -8,9 +7,7 @@ using Content.Server.Stunnable.Components;
|
||||
using Content.Server.Weapon.Ranged.Barrels.Components;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Notification.Managers;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
@@ -27,8 +24,6 @@ using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Server.Weapon.Ranged
|
||||
{
|
||||
@@ -58,16 +53,9 @@ namespace Content.Server.Weapon.Ranged
|
||||
[DataField("clumsyWeaponShotSound")]
|
||||
private SoundSpecifier _clumsyWeaponShotSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/bang.ogg");
|
||||
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
// This also requires changing the dictionary type and modifying TryFire(), which uses it.
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("clumsyDamage")]
|
||||
public Dictionary<string, int> ClumsyDamage { get; set; } = new()
|
||||
{
|
||||
{ "Blunt", 10 },
|
||||
{ "Heat", 5 }
|
||||
};
|
||||
public DamageSpecifier? ClumsyDamage;
|
||||
|
||||
public Func<bool>? WeaponCanFireHandler;
|
||||
public Func<IEntity, bool>? UserCanFireHandler;
|
||||
@@ -179,16 +167,10 @@ namespace Content.Server.Weapon.Ranged
|
||||
|
||||
_lastFireTime = curTime;
|
||||
|
||||
if (ClumsyCheck && ClumsyComponent.TryRollClumsy(user, ClumsyExplodeChance))
|
||||
if (ClumsyCheck && ClumsyDamage != null && ClumsyComponent.TryRollClumsy(user, ClumsyExplodeChance))
|
||||
{
|
||||
//Wound them
|
||||
if (user.TryGetComponent(out IDamageableComponent? health))
|
||||
{
|
||||
foreach (KeyValuePair<string, int> damage in ClumsyDamage)
|
||||
{
|
||||
health.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(damage.Key), damage.Value);
|
||||
}
|
||||
}
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(user.Uid, ClumsyDamage);
|
||||
|
||||
// Knock them down
|
||||
if (user.TryGetComponent(out StunnableComponent? stun))
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.Destructible.Thresholds.Triggers;
|
||||
using Content.Server.Notification;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Rounding;
|
||||
@@ -41,22 +40,7 @@ namespace Content.Server.Window
|
||||
[DataField("knockSound")]
|
||||
private SoundSpecifier _knockSound = new SoundPathSpecifier("/Audio/Effects/glass_knock.ogg");
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
{
|
||||
var current = msg.Damageable.TotalDamage;
|
||||
UpdateVisuals(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisuals(int currentDamage)
|
||||
public void UpdateVisuals(int currentDamage)
|
||||
{
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance) &&
|
||||
Owner.TryGetComponent(out DestructibleComponent? destructible))
|
||||
@@ -75,7 +59,7 @@ namespace Content.Server.Window
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent? damageable) ||
|
||||
if (!Owner.TryGetComponent(out DamageableComponent? damageable) ||
|
||||
!Owner.TryGetComponent(out DestructibleComponent? destructible))
|
||||
{
|
||||
return;
|
||||
|
||||
19
Content.Server/Window/WindowSystem.cs
Normal file
19
Content.Server/Window/WindowSystem.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Window
|
||||
{
|
||||
public class WindowSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<WindowComponent, DamageChangedEvent>(UpdateVisuals);
|
||||
}
|
||||
|
||||
public void UpdateVisuals(EntityUid _, WindowComponent component, DamageChangedEvent args)
|
||||
{
|
||||
component.UpdateVisuals(args.Damageable.TotalDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using Content.Shared.Body.Preset;
|
||||
using Content.Shared.Body.Slot;
|
||||
using Content.Shared.Body.Template;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Standing;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -73,23 +73,6 @@ namespace Content.Shared.Body.Components
|
||||
|
||||
public SharedBodyPartComponent? CenterPart => CenterSlot?.Part;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of damage to deal when all vital organs are removed.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("vitalPartsRemovedDamage")]
|
||||
public int VitalPartsRemovedDamage { get; set; } = 300!;
|
||||
|
||||
/// <summary>
|
||||
/// Damage type to deal when all vital organs are removed.
|
||||
/// </summary>
|
||||
// TODO PROTOTYPE Replace this datafield variable with prototype references, once they are supported.
|
||||
[ViewVariables]
|
||||
[DataField("vitalPartsRemovedDamageType")]
|
||||
private string _vitalPartsRemovedDamageTypeID { get; set; } = "Bloodloss"!;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageTypePrototype VitalPartsRemovedDamageType = default!;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -98,7 +81,6 @@ namespace Content.Shared.Body.Components
|
||||
// TODO BODY Move to template or somewhere else
|
||||
if (TemplateId != null)
|
||||
{
|
||||
VitalPartsRemovedDamageType = _prototypeManager.Index<DamageTypePrototype>(_vitalPartsRemovedDamageTypeID);
|
||||
var template = _prototypeManager.Index<BodyTemplatePrototype>(TemplateId);
|
||||
|
||||
foreach (var (id, partType) in template.Slots)
|
||||
@@ -207,13 +189,11 @@ namespace Content.Shared.Body.Components
|
||||
EntitySystem.Get<StandingStateSystem>().Down(Owner);
|
||||
}
|
||||
|
||||
// creadth: immediately kill entity if last vital part removed
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
if (part.IsVital && SlotParts.Count(x => x.Value.PartType == part.PartType) == 0)
|
||||
{
|
||||
damageable.TryChangeDamage(VitalPartsRemovedDamageType, VitalPartsRemovedDamage, true); // TODO BODY KILL
|
||||
}
|
||||
// TODO BODY SYSTEM KILL : Find a more elegant way of killing em than just dumping bloodloss damage.
|
||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Bloodloss"), 300);
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(part.Owner.Uid, damage);
|
||||
}
|
||||
|
||||
OnBodyChanged();
|
||||
@@ -490,6 +470,7 @@ namespace Content.Shared.Body.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnBodyChanged()
|
||||
{
|
||||
Dirty();
|
||||
|
||||
@@ -1,499 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Acts;
|
||||
using Content.Shared.Damage.Container;
|
||||
using Content.Shared.Damage.Resistances;
|
||||
using Content.Shared.Radiation;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that allows attached entities to take damage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The supported damage types are specified using a <see cref="DamageContainerPrototype"/>s. DamageContainers
|
||||
/// are effectively a dictionary of damage types and damage numbers, along with functions to modify them. Damage
|
||||
/// groups are collections of damage types. A damage group is 'applicable' to a damageable component if it
|
||||
/// supports at least one damage type in that group. A subset of these groups may be 'fully supported' when every
|
||||
/// member of the group is supported by the container. This basic version never dies (thus can take an
|
||||
/// indefinite amount of damage).
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
[NetworkedComponent()]
|
||||
public class DamageableComponent : Component, IDamageableComponent, IRadiationAct, ISerializationHooks
|
||||
{
|
||||
public override string Name => "Damageable";
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The main damage dictionary. All the damage information is stored in this dictionary with <see cref="DamageTypePrototype"/> keys.
|
||||
/// </summary>
|
||||
private Dictionary<DamageTypePrototype, int> _damageDict = new();
|
||||
|
||||
[DataField("resistances")]
|
||||
public string ResistanceSetId { get; set; } = "defaultResistances";
|
||||
|
||||
[ViewVariables] public ResistanceSet Resistances { get; set; } = new();
|
||||
|
||||
// TODO DAMAGE Use as default values, specify overrides in a separate property through yaml for better (de)serialization
|
||||
[ViewVariables]
|
||||
[DataField("damageContainer")]
|
||||
public string DamageContainerId { get; set; } = "metallicDamageContainer";
|
||||
|
||||
// TODO DAMAGE Cache this
|
||||
// When moving logic from damageableComponent --> Damage System, make damageSystem update these on damage change.
|
||||
[ViewVariables] public int TotalDamage => _damageDict.Values.Sum();
|
||||
[ViewVariables] public IReadOnlyDictionary<DamageTypePrototype, int> GetDamagePerType => _damageDict;
|
||||
[ViewVariables] public IReadOnlyDictionary<DamageGroupPrototype, int> GetDamagePerApplicableGroup => DamageTypeDictToDamageGroupDict(_damageDict, ApplicableDamageGroups);
|
||||
[ViewVariables] public IReadOnlyDictionary<DamageGroupPrototype, int> GetDamagePerFullySupportedGroup => DamageTypeDictToDamageGroupDict(_damageDict, FullySupportedDamageGroups);
|
||||
|
||||
// Whenever sending over network, also need a <string, int> dictionary
|
||||
// TODO DAMAGE MAYBE Cache this?
|
||||
public IReadOnlyDictionary<string, int> GetDamagePerApplicableGroupIDs => ConvertDictKeysToIDs(GetDamagePerApplicableGroup);
|
||||
public IReadOnlyDictionary<string, int> GetDamagePerFullySupportedGroupIDs => ConvertDictKeysToIDs(GetDamagePerFullySupportedGroup);
|
||||
public IReadOnlyDictionary<string, int> GetDamagePerTypeIDs => ConvertDictKeysToIDs(_damageDict);
|
||||
|
||||
// TODO PROTOTYPE Replace these datafield variables with prototype references, once they are supported.
|
||||
// Also requires appropriate changes in OnExplosion() and RadiationAct()
|
||||
[ViewVariables]
|
||||
[DataField("radiationDamageTypes")]
|
||||
public List<string> RadiationDamageTypeIDs { get; set; } = new() {"Radiation"};
|
||||
[ViewVariables]
|
||||
[DataField("explosionDamageTypes")]
|
||||
public List<string> ExplosionDamageTypeIDs { get; set; } = new() { "Piercing", "Heat" };
|
||||
|
||||
public HashSet<DamageGroupPrototype> ApplicableDamageGroups { get; } = new();
|
||||
|
||||
public HashSet<DamageGroupPrototype> FullySupportedDamageGroups { get; } = new();
|
||||
|
||||
public HashSet<DamageTypePrototype> SupportedDamageTypes { get; } = new();
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// TODO DAMAGE Serialize damage done and resistance changes
|
||||
var damageContainerPrototype = _prototypeManager.Index<DamageContainerPrototype>(DamageContainerId);
|
||||
|
||||
ApplicableDamageGroups.Clear();
|
||||
FullySupportedDamageGroups.Clear();
|
||||
SupportedDamageTypes.Clear();
|
||||
|
||||
//Get Damage groups/types from the DamageContainerPrototype.
|
||||
DamageContainerId = damageContainerPrototype.ID;
|
||||
ApplicableDamageGroups.UnionWith(damageContainerPrototype.ApplicableDamageGroups);
|
||||
FullySupportedDamageGroups.UnionWith(damageContainerPrototype.FullySupportedDamageGroups);
|
||||
SupportedDamageTypes.UnionWith(damageContainerPrototype.SupportedDamageTypes);
|
||||
|
||||
//initialize damage dictionary 0 damage
|
||||
_damageDict = new(SupportedDamageTypes.Count);
|
||||
foreach (var type in SupportedDamageTypes)
|
||||
{
|
||||
_damageDict.Add(type, 0);
|
||||
}
|
||||
|
||||
Resistances = new ResistanceSet(_prototypeManager.Index<ResistanceSetPrototype>(ResistanceSetId));
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
ForceHealthChangedEvent();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
return new DamageableComponentState(GetDamagePerTypeIDs);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (!(curState is DamageableComponentState state))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_damageDict.Clear();
|
||||
|
||||
foreach (var (type, damage) in state.DamageDict)
|
||||
{
|
||||
_damageDict[_prototypeManager.Index<DamageTypePrototype>(type)] = damage;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetDamage(DamageTypePrototype type)
|
||||
{
|
||||
return GetDamagePerType.GetValueOrDefault(type);
|
||||
}
|
||||
|
||||
public bool TryGetDamage(DamageTypePrototype type, out int damage)
|
||||
{
|
||||
return GetDamagePerType.TryGetValue(type, out damage);
|
||||
}
|
||||
|
||||
public int GetDamage(DamageGroupPrototype group)
|
||||
{
|
||||
return GetDamagePerApplicableGroup.GetValueOrDefault(group);
|
||||
}
|
||||
|
||||
public bool TryGetDamage(DamageGroupPrototype group, out int damage)
|
||||
{
|
||||
return GetDamagePerApplicableGroup.TryGetValue(group, out damage);
|
||||
}
|
||||
|
||||
public bool IsApplicableDamageGroup(DamageGroupPrototype group)
|
||||
{
|
||||
return ApplicableDamageGroups.Contains(group);
|
||||
}
|
||||
|
||||
public bool IsFullySupportedDamageGroup(DamageGroupPrototype group)
|
||||
{
|
||||
return FullySupportedDamageGroups.Contains(group);
|
||||
}
|
||||
|
||||
public bool IsSupportedDamageType(DamageTypePrototype type)
|
||||
{
|
||||
return SupportedDamageTypes.Contains(type);
|
||||
}
|
||||
|
||||
public bool TrySetDamage(DamageGroupPrototype group, int newValue)
|
||||
{
|
||||
if (!ApplicableDamageGroups.Contains(group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newValue < 0)
|
||||
{
|
||||
// invalid value
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
TrySetDamage(type, newValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetAllDamage(int newValue)
|
||||
{
|
||||
if (newValue < 0)
|
||||
{
|
||||
// invalid value
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var type in SupportedDamageTypes)
|
||||
{
|
||||
TrySetDamage(type, newValue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryChangeDamage(DamageTypePrototype type, int amount, bool ignoreDamageResistances = false)
|
||||
{
|
||||
// Check if damage type is supported, and get the current value if it is.
|
||||
if (!GetDamagePerType.TryGetValue(type, out var current))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply resistances (does nothing if amount<0)
|
||||
var finalDamage = amount;
|
||||
if (!ignoreDamageResistances)
|
||||
{
|
||||
finalDamage = Resistances.CalculateDamage(type, amount);
|
||||
}
|
||||
|
||||
if (finalDamage == 0)
|
||||
return false;
|
||||
|
||||
// Are we healing below zero?
|
||||
if (current + finalDamage < 0)
|
||||
{
|
||||
if (current == 0)
|
||||
// Damage type is supported, but there is nothing to do
|
||||
return false;
|
||||
|
||||
// Cap healing down to zero
|
||||
_damageDict[type] = 0;
|
||||
finalDamage = -current;
|
||||
}
|
||||
else
|
||||
{
|
||||
_damageDict[type] = current + finalDamage;
|
||||
}
|
||||
|
||||
current = _damageDict[type];
|
||||
|
||||
var datum = new DamageChangeData(type, current, finalDamage);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryChangeDamage(DamageGroupPrototype group, int amount, bool ignoreDamageResistances = false)
|
||||
{
|
||||
var types = group.DamageTypes.ToArray();
|
||||
|
||||
if (amount < 0)
|
||||
{
|
||||
// We are Healing. Keep track of how much we can hand out (with a better var name for readability).
|
||||
var availableHealing = -amount;
|
||||
|
||||
// Get total group damage.
|
||||
var damageToHeal = GetDamagePerApplicableGroup[group];
|
||||
|
||||
// Is there any damage to even heal?
|
||||
if (damageToHeal == 0)
|
||||
return false;
|
||||
|
||||
// If total healing is more than there is damage, just set to 0 and return.
|
||||
if (damageToHeal <= availableHealing)
|
||||
{
|
||||
TrySetDamage(group, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Partially heal each damage group
|
||||
int healing, damage;
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (!_damageDict.TryGetValue(type, out damage))
|
||||
{
|
||||
// Damage Type is not supported. Continue without reducing availableHealing
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply healing to the damage type. The healing amount may be zero if either damage==0, or if
|
||||
// integer rounding made it zero (i.e., damage is small)
|
||||
healing = (availableHealing * damage) / damageToHeal;
|
||||
TryChangeDamage(type, -healing, ignoreDamageResistances);
|
||||
|
||||
// remove this damage type from the damage we consider for future loops, regardless of how much we
|
||||
// actually healed this type.
|
||||
damageToHeal -= damage;
|
||||
availableHealing -= healing;
|
||||
|
||||
// If we now healed all the damage, exit. otherwise 1/0 and universe explodes.
|
||||
if (damageToHeal == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Damage type is supported, there was damage to heal, and resistances were ignored
|
||||
// --> Damage must have changed
|
||||
return true;
|
||||
}
|
||||
else if (amount > 0)
|
||||
{
|
||||
// Resistances may result in no actual damage change. We need to keep track if any damage got through.
|
||||
var damageChanged = false;
|
||||
|
||||
// We are adding damage. Keep track of how much we can dish out (with a better var name for readability).
|
||||
var availableDamage = amount;
|
||||
|
||||
// How many damage types do we have to distribute over?.
|
||||
var numberDamageTypes = types.Length;
|
||||
|
||||
// Apply damage to each damage group
|
||||
int damage;
|
||||
foreach (var type in types)
|
||||
{
|
||||
// Distribute the remaining damage over the remaining damage types.
|
||||
damage = availableDamage / numberDamageTypes;
|
||||
|
||||
// Try apply the damage type. If damage type is not supported, this has no effect.
|
||||
// We also use the return value to check whether any damage has changed
|
||||
damageChanged = TryChangeDamage(type, damage, ignoreDamageResistances) || damageChanged;
|
||||
|
||||
// regardless of whether we dealt damage, reduce the amount to distribute.
|
||||
availableDamage -= damage;
|
||||
numberDamageTypes -= 1;
|
||||
|
||||
}
|
||||
return damageChanged;
|
||||
}
|
||||
|
||||
// amount==0 no damage change.
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TrySetDamage(DamageTypePrototype type, int newValue)
|
||||
{
|
||||
if (!_damageDict.TryGetValue(type, out var oldValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newValue < 0)
|
||||
{
|
||||
// invalid value
|
||||
return false;
|
||||
}
|
||||
|
||||
if (oldValue == newValue)
|
||||
{
|
||||
// No health change.
|
||||
// But we are trying to set, not trying to change.
|
||||
return true;
|
||||
}
|
||||
|
||||
_damageDict[type] = newValue;
|
||||
|
||||
var delta = newValue - oldValue;
|
||||
var datum = new DamageChangeData(type, 0, delta);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ForceHealthChangedEvent()
|
||||
{
|
||||
var data = new List<DamageChangeData>();
|
||||
|
||||
foreach (var type in SupportedDamageTypes)
|
||||
{
|
||||
var damage = GetDamage(type);
|
||||
var datum = new DamageChangeData(type, damage, 0);
|
||||
data.Add(datum);
|
||||
}
|
||||
|
||||
OnHealthChanged(data);
|
||||
}
|
||||
|
||||
private void OnHealthChanged(List<DamageChangeData> changes)
|
||||
{
|
||||
var args = new DamageChangedEventArgs(this, changes);
|
||||
OnHealthChanged(args);
|
||||
}
|
||||
|
||||
protected virtual void OnHealthChanged(DamageChangedEventArgs e)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, e);
|
||||
|
||||
var message = new DamageChangedMessage(this, e.Data);
|
||||
SendMessage(message);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public void RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
|
||||
{
|
||||
var totalDamage = Math.Max((int)(frameTime * radiation.RadsPerSecond), 1);
|
||||
|
||||
foreach (var typeID in RadiationDamageTypeIDs)
|
||||
{
|
||||
TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(typeID), totalDamage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var damage = eventArgs.Severity switch
|
||||
{
|
||||
ExplosionSeverity.Light => 20,
|
||||
ExplosionSeverity.Heavy => 60,
|
||||
ExplosionSeverity.Destruction => 250,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
foreach (var typeID in ExplosionDamageTypeIDs)
|
||||
{
|
||||
TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(typeID), damage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take a dictionary with <see cref="IPrototype"/> keys and return a dictionary using <see cref="IPrototype.ID"/> as keys
|
||||
/// instead.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful when sending damage type and group prototypes dictionaries over the network.
|
||||
/// </remarks>
|
||||
public static IReadOnlyDictionary<string, int>
|
||||
ConvertDictKeysToIDs<TPrototype>(IReadOnlyDictionary<TPrototype, int> prototypeDict)
|
||||
where TPrototype : IPrototype
|
||||
{
|
||||
Dictionary<string, int> idDict = new(prototypeDict.Count);
|
||||
foreach (var entry in prototypeDict)
|
||||
{
|
||||
idDict.Add(entry.Key.ID, entry.Value);
|
||||
}
|
||||
return idDict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a dictionary with damage type keys to a dictionary of damage groups keys.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Takes a dictionary with damage types as keys and integers as values, and an iterable list of damage
|
||||
/// groups. Returns a dictionary with damage group keys, with values calculated by adding up the values for
|
||||
/// each damage type in that group. If a damage type is associated with more than one supported damage
|
||||
/// group, it will contribute to the total of each group. Conversely, some damage types may not contribute
|
||||
/// to the new dictionary if their associated group(s) are not in given list of groups.
|
||||
/// </remarks>
|
||||
public static IReadOnlyDictionary<DamageGroupPrototype, int>
|
||||
DamageTypeDictToDamageGroupDict(IReadOnlyDictionary<DamageTypePrototype, int> damageTypeDict, IEnumerable<DamageGroupPrototype> groupKeys)
|
||||
{
|
||||
var damageGroupDict = new Dictionary<DamageGroupPrototype, int>();
|
||||
int damageGroupSumDamage, damageTypeDamage;
|
||||
// iterate over the list of group keys for our new dictionary
|
||||
foreach (var group in groupKeys)
|
||||
{
|
||||
// For each damage type in this group, add up the damage present in the given dictionary
|
||||
damageGroupSumDamage = 0;
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
// if the damage type is in the dictionary, add it's damage to the group total.
|
||||
if (damageTypeDict.TryGetValue(type, out damageTypeDamage))
|
||||
{
|
||||
damageGroupSumDamage += damageTypeDamage;
|
||||
}
|
||||
}
|
||||
damageGroupDict.Add(group, damageGroupSumDamage);
|
||||
}
|
||||
return damageGroupDict;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageableComponentState : ComponentState
|
||||
{
|
||||
public readonly IReadOnlyDictionary<string, int> DamageDict;
|
||||
|
||||
public DamageableComponentState(IReadOnlyDictionary<string, int> damageDict)
|
||||
|
||||
{
|
||||
DamageDict = damageDict;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Acts;
|
||||
using Content.Shared.Damage.Resistances;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Damage.Components
|
||||
{
|
||||
public interface IDamageableComponent : IComponent, IExAct
|
||||
{
|
||||
/// <summary>
|
||||
/// The sum of all damages types in the DamageableComponent.
|
||||
/// </summary>
|
||||
int TotalDamage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of the damage in the container, indexed by applicable <see cref="DamageGroupPrototype"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values represent the sum of all damage in each group. If a supported damage type is a member of more than one group, it will contribute to each one.
|
||||
/// Therefore, the sum of the values may be greater than the sum of the values in the dictionary returned by <see cref="GetDamagePerType"/>
|
||||
/// </remarks>
|
||||
IReadOnlyDictionary<DamageGroupPrototype, int> GetDamagePerApplicableGroup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of the damage in the container, indexed by fully supported instances of <see
|
||||
/// cref="DamageGroupPrototype"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values represent the sum of all damage in each group. As the damage container may have some damage
|
||||
/// types that are not part of a fully supported damage group, the sum of the values may be less of the values
|
||||
/// in the dictionary returned by <see cref="GetDamagePerType"/>. On the other hand, if a supported damage type
|
||||
/// is a member of more than one group, it will contribute to each one. Therefore, the sum may also be greater
|
||||
/// instead.
|
||||
/// </remarks>
|
||||
IReadOnlyDictionary<DamageGroupPrototype, int> GetDamagePerFullySupportedGroup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary of the damage in the container, indexed by <see cref="DamageTypePrototype"/>.
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<DamageTypePrototype, int> GetDamagePerType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="GetDamagePerApplicableGroup"/>, but indexed by <see cref="DamageGroupPrototype.ID"/>
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, int> GetDamagePerApplicableGroupIDs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="GetDamagePerFullySupportedGroup"/>, but indexed by <see cref="DamageGroupPrototype.ID"/>
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, int> GetDamagePerFullySupportedGroupIDs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="GetDamagePerType"/>, but indexed by <see cref="DamageTypePrototype.ID"/>
|
||||
/// </summary>
|
||||
IReadOnlyDictionary<string, int> GetDamagePerTypeIDs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of damage types supported by this DamageableComponent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each of these damage types is fully supported. If any of these damage types is a
|
||||
/// member of a damage group, these groups are represented in <see cref="ApplicableDamageGroups"></see>
|
||||
/// </remarks>
|
||||
HashSet<DamageTypePrototype> SupportedDamageTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of damage groups that are fully supported by DamageableComponent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This describes what damage groups this damage container explicitly supports. It supports every damage type
|
||||
/// contained in these damage groups. It may also support other damage types not in these groups. To see all
|
||||
/// damage types <see cref="SupportedDamageTypes"/>, and to see all applicable damage groups <see
|
||||
/// cref="ApplicableDamageGroups"/>.
|
||||
/// </remarks>
|
||||
HashSet<DamageGroupPrototype> FullySupportedDamageGroups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Collection of damage groups that could apply damage to this DamageableComponent.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This describes what damage groups could have an effect on this damage container. However not every damage
|
||||
/// group has to be fully supported. For example, the container may support ONLY the piercing damage type. It should
|
||||
/// therefore be affected by instances of brute damage, but does not necessarily support blunt or slash damage.
|
||||
/// For a list of supported damage types, see <see cref="SupportedDamageTypes"/>.
|
||||
/// </remarks>
|
||||
HashSet<DamageGroupPrototype> ApplicableDamageGroups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The resistances of this component.
|
||||
/// </summary>
|
||||
ResistanceSet Resistances { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the amount of damage of a type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get the damage of.</param>
|
||||
/// <param name="damage">The amount of damage of that type.</param>
|
||||
/// <returns>
|
||||
/// True if the given <see cref="type"/> is supported, false otherwise.
|
||||
/// </returns>
|
||||
bool TryGetDamage(DamageTypePrototype type, out int damage);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amount of damage of a given type, or zero if it is not supported.
|
||||
/// </summary>
|
||||
int GetDamage(DamageTypePrototype type);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the total amount of damage in a damage group.
|
||||
/// </summary>
|
||||
/// <param name="group">The group to get the damage of.</param>
|
||||
/// <param name="damage">The amount of damage in that group.</param>
|
||||
/// <returns>
|
||||
/// True if the given group is applicable to this container, false otherwise.
|
||||
/// </returns>
|
||||
bool TryGetDamage(DamageGroupPrototype group, out int damage);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amount of damage present in an applicable group, or zero if no members are supported.
|
||||
/// </summary>
|
||||
int GetDamage(DamageGroupPrototype group);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to change the specified <see cref="DamageTypePrototype"/>, applying
|
||||
/// resistance values only if it is dealing damage.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of damage being changed.</param>
|
||||
/// <param name="amount">
|
||||
/// Amount of damage being received (positive for damage, negative for heals).
|
||||
/// </param>
|
||||
/// <param name="ignoreDamageResistances">
|
||||
/// Whether or not to ignore resistances when taking damage.
|
||||
/// Healing always ignores resistances, regardless of this input.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// False if the given type is not supported or no damage change occurred; true otherwise.
|
||||
/// </returns>
|
||||
bool TryChangeDamage(DamageTypePrototype type, int amount, bool ignoreDamageResistances = false);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to change damage of the specified <see cref="DamageGroupPrototype"/>, applying resistance values
|
||||
/// only if it is damage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If dealing damage, this spreads the damage change amount evenly between the <see
|
||||
/// cref="DamageTypePrototype"></see>s in this group (subject to integer rounding). If only a subset of the
|
||||
/// damage types in the group are actually supported, then the total damage dealt may be less than expected
|
||||
/// (unsupported damage is ignored).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If healing damage, this spreads the damage change proportional to the current damage value of each <see
|
||||
/// cref="DamageTypePrototype"></see> (subject to integer rounding). If there is less damage than is being
|
||||
/// healed, some healing is wasted. Unsupported damage types do not waste healing.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="group">group of damage being changed.</param>
|
||||
/// <param name="amount">
|
||||
/// Amount of damage being received (positive for damage, negative for heals).
|
||||
/// </param>
|
||||
/// <param name="ignoreDamageResistances">
|
||||
/// Whether to ignore resistances when taking damage. Healing always ignores resistances, regardless of this
|
||||
/// input.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns false if the given group is not applicable or no damage change occurred; true otherwise.
|
||||
/// </returns>
|
||||
bool TryChangeDamage(DamageGroupPrototype group, int amount, bool ignoreDamageResistances = false);
|
||||
|
||||
/// <summary>
|
||||
/// Forcefully sets the specified <see cref="DamageTypePrototype"/> to the given value, ignoring resistance
|
||||
/// values.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of damage being set.</param>
|
||||
/// <param name="newValue">New damage value to be set.</param>
|
||||
/// <returns>
|
||||
/// Returns false if a given type is not supported or a negative value is provided; true otherwise.
|
||||
/// </returns>
|
||||
bool TrySetDamage(DamageTypePrototype type, int newValue);
|
||||
|
||||
/// <summary>
|
||||
/// Forcefully sets all damage types in a specified damage group using <see cref="TrySetDamage"></see>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that the actual damage of this group will be equal to the given value times the number damage group
|
||||
/// members that this container supports.
|
||||
/// </remarks>
|
||||
/// <param name="group">Group of damage being set.</param>
|
||||
/// <param name="newValue">New damage value to be set.</param>
|
||||
/// <returns>
|
||||
/// Returns false if the given group is not applicable or a negative value is provided; true otherwise.
|
||||
/// </returns>
|
||||
bool TrySetDamage(DamageGroupPrototype group, int newValue);
|
||||
|
||||
/// <summary>
|
||||
/// Sets all supported damage types to specified value using <see cref="TrySetDamage"></see>.
|
||||
/// </summary>
|
||||
/// <param name="newValue">New damage value to be set.</param>
|
||||
/// <returns>
|
||||
/// Returns false if a negative value is provided; true otherwise.
|
||||
/// </returns>
|
||||
bool TrySetAllDamage(int newValue);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given damage group is applicable to this damage container.
|
||||
/// </summary>
|
||||
public bool IsApplicableDamageGroup(DamageGroupPrototype group);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given damage group is fully supported by this damage container.
|
||||
/// </summary>
|
||||
public bool IsFullySupportedDamageGroup(DamageGroupPrototype group);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given damage type is supported by this damage container.
|
||||
/// </summary>
|
||||
public bool IsSupportedDamageType(DamageTypePrototype type);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the HealthChangedEvent with the current values of health.
|
||||
/// </summary>
|
||||
void ForceHealthChangedEvent();
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage.Container
|
||||
{
|
||||
/// <summary>
|
||||
/// A damage container which can be used to specify support for various damage types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is effectively just a list of damage types that can be specified in YAML files using both damage types
|
||||
/// and damage groups. Currently this is only used to specify what damage types a <see
|
||||
/// cref="Components.DamageableComponent"/> should support.
|
||||
/// </remarks>
|
||||
[Prototype("damageContainer")]
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageContainerPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
private IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("id", required: true)]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this DamageContainerPrototype will support ALL damage types and groups. If true,
|
||||
/// ignore all other options.
|
||||
/// </summary>
|
||||
[DataField("supportAll")] private bool _supportAll;
|
||||
|
||||
[DataField("supportedGroups")] private HashSet<string> _supportedDamageGroupIDs = new();
|
||||
[DataField("supportedTypes")] private HashSet<string> _supportedDamageTypeIDs = new();
|
||||
|
||||
private HashSet<DamageGroupPrototype> _applicableDamageGroups = new();
|
||||
private HashSet<DamageGroupPrototype> _fullySupportedDamageGroups = new();
|
||||
private HashSet<DamageTypePrototype> _supportedDamageTypes = new();
|
||||
|
||||
// TODO NET 5 IReadOnlySet
|
||||
|
||||
/// <summary>
|
||||
/// Collection of damage groups that can affect this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This describes what damage groups can have an effect on this damage container. However not every damage
|
||||
/// group has to be fully supported. For example, the container may support ONLY the piercing damage type.
|
||||
/// It should therefore be affected by instances of brute group damage, but does not necessarily support
|
||||
/// blunt or slash damage. If damage containers are only specified by supported damage groups, and every
|
||||
/// damage type is in only one damage group, then SupportedDamageTypes should be equal to
|
||||
/// ApplicableDamageGroups. For a list of supported damage types, see <see cref="SupportedDamageTypes"/>.
|
||||
/// </remarks>
|
||||
[ViewVariables] public IReadOnlyCollection<DamageGroupPrototype> ApplicableDamageGroups => _applicableDamageGroups;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of damage groups that are fully supported by this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This describes what damage groups this damage container explicitly supports. It supports every damage
|
||||
/// type contained in these damage groups. It may also support other damage types not in these groups. To
|
||||
/// see all damage types <see cref="SupportedDamageTypes"/>, and to see all applicable damage groups <see
|
||||
/// cref="ApplicableDamageGroups"/>.
|
||||
/// </remarks>
|
||||
[ViewVariables] public IReadOnlyCollection<DamageGroupPrototype> FullySupportedDamageGroups => _fullySupportedDamageGroups;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of damage types supported by this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each of these damage types is fully supported by the DamageContainer. If any of these damage types is a
|
||||
/// member of a damage group, these groups are added to <see cref="ApplicableDamageGroups"></see>
|
||||
/// </remarks>
|
||||
[ViewVariables] public IReadOnlyCollection<DamageTypePrototype> SupportedDamageTypes => _supportedDamageTypes;
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
_prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
if (_supportAll)
|
||||
{
|
||||
foreach (var group in _prototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
_applicableDamageGroups.Add(group);
|
||||
_fullySupportedDamageGroups.Add(group);
|
||||
}
|
||||
foreach (var type in _prototypeManager.EnumeratePrototypes<DamageTypePrototype>())
|
||||
{
|
||||
_supportedDamageTypes.Add(type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Add fully supported damage groups
|
||||
foreach (var groupID in _supportedDamageGroupIDs)
|
||||
{
|
||||
var group = _prototypeManager.Index<DamageGroupPrototype>(groupID);
|
||||
_fullySupportedDamageGroups.Add(group);
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
_supportedDamageTypes.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
// Add individual damage types, that are either not part of a group, or whose groups are (possibly) not fully supported
|
||||
foreach (var supportedTypeID in _supportedDamageTypeIDs)
|
||||
{
|
||||
var type = _prototypeManager.Index<DamageTypePrototype>(supportedTypeID);
|
||||
_supportedDamageTypes.Add(type);
|
||||
}
|
||||
|
||||
// For whatever reason, someone may have listed all members of a group as supported instead of just listing
|
||||
// the group as supported. Check for this.
|
||||
foreach (var group in _prototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
if (_fullySupportedDamageGroups.Contains(group))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// The group is not in the list of fully supported groups. Should it be?
|
||||
var allMembersSupported = true;
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
if (!_supportedDamageTypes.Contains(type))
|
||||
{
|
||||
// not all members are supported
|
||||
allMembersSupported = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allMembersSupported) {
|
||||
// All members are supported. The silly goose should have just used a damage group.
|
||||
_fullySupportedDamageGroups.Add(group);
|
||||
}
|
||||
}
|
||||
|
||||
// For each supported damage type, check whether it is in any existing group, If it is add it to _applicableDamageGroups
|
||||
foreach (var type in _supportedDamageTypes)
|
||||
{
|
||||
foreach (var group in _prototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
if (group.DamageTypes.Contains(type))
|
||||
{
|
||||
_applicableDamageGroups.Add(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Data class with information on how the value of a
|
||||
/// single <see cref="DamageTypePrototype"/> has changed.
|
||||
/// </summary>
|
||||
public struct DamageChangeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of damage that changed.
|
||||
/// </summary>
|
||||
public DamageTypePrototype Type;
|
||||
|
||||
/// <summary>
|
||||
/// The new current value for that damage.
|
||||
/// </summary>
|
||||
public int NewValue;
|
||||
|
||||
/// <summary>
|
||||
/// How much the health value changed from its last value (negative is heals, positive is damage).
|
||||
/// </summary>
|
||||
public int Delta;
|
||||
|
||||
public DamageChangeData(DamageTypePrototype type, int newValue, int delta)
|
||||
{
|
||||
Type = type;
|
||||
NewValue = newValue;
|
||||
Delta = delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage.Components;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
public class DamageChangedEventArgs : EventArgs
|
||||
{
|
||||
public DamageChangedEventArgs(IDamageableComponent damageable, IReadOnlyList<DamageChangeData> data)
|
||||
{
|
||||
Damageable = damageable;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public DamageChangedEventArgs(IDamageableComponent damageable, DamageTypePrototype type, int newValue, int delta)
|
||||
{
|
||||
Damageable = damageable;
|
||||
|
||||
var datum = new DamageChangeData(type, newValue, delta);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="IDamageableComponent"/> that invoked the event.
|
||||
/// </summary>
|
||||
public IDamageableComponent Damageable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List containing data on each <see cref="DamageTypePrototype"/> that was changed.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DamageChangeData> Data { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
public class DamageChangedMessage : ComponentMessage
|
||||
{
|
||||
public DamageChangedMessage(IDamageableComponent damageable, IReadOnlyList<DamageChangeData> data)
|
||||
{
|
||||
Damageable = damageable;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public DamageChangedMessage(IDamageableComponent damageable, DamageTypePrototype type, int newValue, int delta)
|
||||
{
|
||||
Damageable = damageable;
|
||||
|
||||
var datum = new DamageChangeData(type, newValue, delta);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="IDamageableComponent"/> that invoked the event.
|
||||
/// </summary>
|
||||
public IDamageableComponent Damageable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List containing data on each <see cref="DamageTypePrototype"/> that was changed.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DamageChangeData> Data { get; }
|
||||
|
||||
public bool TookDamage
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var datum in Data)
|
||||
{
|
||||
if (datum.Delta > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// A Group of <see cref="DamageTypePrototype"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These groups can be used to specify supported damage types of a <see
|
||||
/// cref="Container.DamageContainerPrototype"/>, or to change/get/set damage in a <see
|
||||
/// cref="Components.DamageableComponent"/>.
|
||||
/// </remarks>
|
||||
[Prototype("damageGroup")]
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageGroupPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
private IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[DataField("id", required: true)] public string ID { get; } = default!;
|
||||
|
||||
[DataField("damageTypes", required: true)]
|
||||
public List<string> TypeIDs { get; } = default!;
|
||||
|
||||
public HashSet<DamageTypePrototype> DamageTypes { get; } = new();
|
||||
|
||||
// Create set of damage types
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
_prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
foreach (var typeID in TypeIDs)
|
||||
{
|
||||
DamageTypes.Add(_prototypeManager.Index<DamageTypePrototype>(typeID));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
375
Content.Shared/Damage/DamageSpecifier.cs
Normal file
375
Content.Shared/Damage/DamageSpecifier.cs
Normal file
@@ -0,0 +1,375 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
// TODO DAMAGE UNITS Move this whole class away from, using integers. Also get rid of a lot of the rounding. Just
|
||||
// use DamageUnit math operators.
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a collection of damage types and damage values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual damage information is stored in <see cref="DamageDict"/>. This class provides
|
||||
/// functions to apply resistance sets and supports basic math operations to modify this dictionary.
|
||||
/// </remarks>
|
||||
[DataDefinition]
|
||||
public class DamageSpecifier
|
||||
{
|
||||
[DataField("types", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, DamageTypePrototype>))]
|
||||
private readonly Dictionary<string,int>? _damageTypeDictionary;
|
||||
|
||||
[DataField("groups", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<int, DamageGroupPrototype>))]
|
||||
private readonly Dictionary<string, int>? _damageGroupDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// Main DamageSpecifier dictionary. Most DamageSpecifier functions exist to somehow modifying this.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Dictionary<string, int> DamageDict
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_damageDict == null)
|
||||
DeserializeDamage();
|
||||
return _damageDict!;
|
||||
}
|
||||
set => _damageDict = value;
|
||||
}
|
||||
private Dictionary<string, int>? _damageDict;
|
||||
|
||||
/// <summary>
|
||||
/// Sum of the damage values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this being zero does not mean this damage has no effect. Healing in one type may cancel damage
|
||||
/// in another. For this purpose, you should instead use <see cref="TrimZeros()"/> and then check the <see
|
||||
/// cref="Empty"/> property.
|
||||
/// </remarks>
|
||||
public int Total => DamageDict.Values.Sum();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this damage specifier has any entries.
|
||||
/// </summary>
|
||||
public bool Empty => DamageDict.Count == 0;
|
||||
|
||||
#region constructors
|
||||
/// <summary>
|
||||
/// Constructor that just results in an empty dictionary.
|
||||
/// </summary>
|
||||
public DamageSpecifier() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes another DamageSpecifier instance and copies it.
|
||||
/// </summary>
|
||||
public DamageSpecifier(DamageSpecifier damageSpec)
|
||||
{
|
||||
DamageDict = new(damageSpec.DamageDict);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a single damage type prototype and a damage value.
|
||||
/// </summary>
|
||||
public DamageSpecifier(DamageTypePrototype type, int value)
|
||||
{
|
||||
DamageDict = new() { { type.ID, value } };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a single damage group prototype and a damage value. The value is divided between members of the damage group.
|
||||
/// </summary>
|
||||
public DamageSpecifier(DamageGroupPrototype group, int value)
|
||||
{
|
||||
_damageGroupDictionary = new() { { group.ID, value } };
|
||||
}
|
||||
#endregion constructors
|
||||
|
||||
/// <summary>
|
||||
/// Combines the damage group and type datafield dictionaries into a single damage dictionary.
|
||||
/// </summary>
|
||||
public void DeserializeDamage()
|
||||
{
|
||||
// Add all the damage types by just copying the type dictionary (if it is not null).
|
||||
if (_damageTypeDictionary != null)
|
||||
{
|
||||
_damageDict = new(_damageTypeDictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
_damageDict = new();
|
||||
}
|
||||
|
||||
if (_damageGroupDictionary == null)
|
||||
return;
|
||||
|
||||
// Then resolve damage groups and add them
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
foreach (var entry in _damageGroupDictionary)
|
||||
{
|
||||
if (!prototypeManager.TryIndex<DamageGroupPrototype>(entry.Key, out var group))
|
||||
{
|
||||
// This can happen if deserialized before prototypes are loaded.
|
||||
Logger.Error($"Unknown damage group given to DamageSpecifier: {entry.Key}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Simply distribute evenly (except for rounding).
|
||||
// We do this by reducing remaining the # of types and damage every loop.
|
||||
var remainingTypes = group.DamageTypes.Count;
|
||||
var remainingDamage = entry.Value;
|
||||
foreach (var damageType in group.DamageTypes)
|
||||
{
|
||||
var damage = remainingDamage / remainingTypes;
|
||||
if (!_damageDict.TryAdd(damageType, damage))
|
||||
{
|
||||
// Key already exists, add values
|
||||
_damageDict[damageType] += damage;
|
||||
}
|
||||
remainingDamage -= damage;
|
||||
remainingTypes -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduce (or increase) damages by applying a resistance set.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only applies resistance to a damage type if it is dealing damage, not healing.
|
||||
/// </remarks>
|
||||
public static DamageSpecifier ApplyResistanceSet(DamageSpecifier damageSpec, ResistanceSetPrototype resistanceSet)
|
||||
{
|
||||
// Make a copy of the given data. Don't modify the one passed to this function. I did this before, and weapons became
|
||||
// duller as you hit walls. Neat, but not intended. And confusing, when you realize your fists don't work no
|
||||
// more cause they're just bloody stumps.
|
||||
DamageSpecifier newDamage = new(damageSpec);
|
||||
|
||||
foreach (var entry in newDamage.DamageDict)
|
||||
{
|
||||
if (entry.Value <= 0) continue;
|
||||
|
||||
float newValue = entry.Value;
|
||||
|
||||
if (resistanceSet.FlatReduction.TryGetValue(entry.Key, out var reduction))
|
||||
{
|
||||
newValue -= reduction;
|
||||
if (newValue <= 0)
|
||||
{
|
||||
// flat reductions cannot heal you
|
||||
newDamage.DamageDict[entry.Key] = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (resistanceSet.Coefficients.TryGetValue(entry.Key, out var coefficient))
|
||||
{
|
||||
// negative coefficients **can** heal you.
|
||||
newValue = MathF.Round(newValue*coefficient, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
newDamage.DamageDict[entry.Key] = (int) newValue;
|
||||
}
|
||||
|
||||
newDamage.TrimZeros();
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove any damage entries with zero damage.
|
||||
/// </summary>
|
||||
public void TrimZeros()
|
||||
{
|
||||
foreach (var (key, value) in DamageDict)
|
||||
{
|
||||
if (value == 0)
|
||||
{
|
||||
DamageDict.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps each damage value to be within the given range.
|
||||
/// </summary>
|
||||
public void Clamp(int minValue = 0, int maxValue = 0)
|
||||
{
|
||||
DebugTools.Assert(minValue < maxValue);
|
||||
ClampMax(maxValue);
|
||||
ClampMin(minValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all damage values to be at least as large as the given number.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this only acts on damage types present in the dictionary. It will not add new damage types.
|
||||
/// </remarks>
|
||||
public void ClampMin(int minValue = 0)
|
||||
{
|
||||
foreach (var (key, value) in DamageDict)
|
||||
{
|
||||
if (value < minValue)
|
||||
{
|
||||
DamageDict[key] = minValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all damage values to be at most some number. Note that if a damage type is not present in the
|
||||
/// dictionary, these will not be added.
|
||||
/// </summary>
|
||||
public void ClampMax(int maxValue = 0)
|
||||
{
|
||||
foreach (var (key, value) in DamageDict)
|
||||
{
|
||||
if (value > maxValue)
|
||||
{
|
||||
DamageDict[key] = maxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This adds the damage values of some other <see cref="DamageSpecifier"/> to the current one without
|
||||
/// adding any new damage types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for <see cref="DamageableComponent"/>s, such that only "supported" damage types are
|
||||
/// actually added to the component. In most other instances, you can just use the addition operator.
|
||||
/// </remarks>
|
||||
public void ExclusiveAdd(DamageSpecifier other)
|
||||
{
|
||||
foreach (var (type, value) in other.DamageDict)
|
||||
{
|
||||
if (DamageDict.ContainsKey(type))
|
||||
{
|
||||
DamageDict[type] += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add up all the damage values for damage types that are members of a given group.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If no members of the group are included in this specifier, returns false.
|
||||
/// </remarks>
|
||||
public bool TryGetDamageInGroup(DamageGroupPrototype group, out int total)
|
||||
{
|
||||
bool containsMemeber = false;
|
||||
total = 0;
|
||||
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
if (DamageDict.TryGetValue(type, out var value))
|
||||
{
|
||||
total += value;
|
||||
containsMemeber = true;
|
||||
}
|
||||
}
|
||||
return containsMemeber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictionary using <see cref="DamageGroupPrototype.ID"/> keys, with values calculated by adding
|
||||
/// up the values for each damage type in that group
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If a damage type is associated with more than one supported damage group, it will contribute to the
|
||||
/// total of each group. If no members of a group are present in this <see cref="DamageSpecifier"/>, the
|
||||
/// group is not included in the resulting dictionary.
|
||||
/// </remarks>
|
||||
public Dictionary<string, int> GetDamagePerGroup()
|
||||
{
|
||||
var damageGroupDict = new Dictionary<string, int>();
|
||||
foreach (var group in IoCManager.Resolve<IPrototypeManager>().EnumeratePrototypes<DamageGroupPrototype>())
|
||||
{
|
||||
if (TryGetDamageInGroup(group, out var value))
|
||||
{
|
||||
damageGroupDict.Add(group.ID, value);
|
||||
}
|
||||
}
|
||||
return damageGroupDict;
|
||||
}
|
||||
|
||||
#region Operators
|
||||
public static DamageSpecifier operator *(DamageSpecifier damageSpec, int factor)
|
||||
{
|
||||
DamageSpecifier newDamage = new();
|
||||
foreach (var entry in damageSpec.DamageDict)
|
||||
{
|
||||
newDamage.DamageDict.Add(entry.Key, entry.Value * factor);
|
||||
}
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
public static DamageSpecifier operator *(DamageSpecifier damageSpec, float factor)
|
||||
{
|
||||
DamageSpecifier newDamage = new();
|
||||
foreach (var entry in damageSpec.DamageDict)
|
||||
{
|
||||
newDamage.DamageDict.Add(entry.Key, (int) MathF.Round(entry.Value * factor, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
public static DamageSpecifier operator /(DamageSpecifier damageSpec, int factor)
|
||||
{
|
||||
DamageSpecifier newDamage = new();
|
||||
foreach (var entry in damageSpec.DamageDict)
|
||||
{
|
||||
newDamage.DamageDict.Add(entry.Key, (int) MathF.Round(entry.Value / (float) factor, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
public static DamageSpecifier operator /(DamageSpecifier damageSpec, float factor)
|
||||
{
|
||||
DamageSpecifier newDamage = new();
|
||||
|
||||
foreach (var entry in damageSpec.DamageDict)
|
||||
{
|
||||
newDamage.DamageDict.Add(entry.Key, (int) MathF.Round(entry.Value / factor, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
public static DamageSpecifier operator +(DamageSpecifier damageSpecA, DamageSpecifier damageSpecB)
|
||||
{
|
||||
// Copy existing dictionary from dataA
|
||||
DamageSpecifier newDamage = new(damageSpecA);
|
||||
|
||||
// Then just add types in B
|
||||
foreach (var entry in damageSpecB.DamageDict)
|
||||
{
|
||||
if (!newDamage.DamageDict.TryAdd(entry.Key, entry.Value))
|
||||
{
|
||||
// Key already exists, add values
|
||||
newDamage.DamageDict[entry.Key] += entry.Value;
|
||||
}
|
||||
}
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
public static DamageSpecifier operator -(DamageSpecifier damageSpecA, DamageSpecifier damageSpecB) => damageSpecA + -damageSpecB;
|
||||
|
||||
public static DamageSpecifier operator +(DamageSpecifier damageSpec) => damageSpec;
|
||||
|
||||
public static DamageSpecifier operator -(DamageSpecifier damageSpec) => damageSpec * -1;
|
||||
|
||||
public static DamageSpecifier operator *(float factor, DamageSpecifier damageSpec) => damageSpec * factor;
|
||||
|
||||
public static DamageSpecifier operator *(int factor, DamageSpecifier damageSpec) => damageSpec * factor;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class DamageSystem : EntitySystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
129
Content.Shared/Damage/DamageableComponent.cs
Normal file
129
Content.Shared/Damage/DamageableComponent.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Acts;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Radiation;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that allows entities to take damage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The supported damage types are specified using a <see cref="DamageContainerPrototype"/>s. DamageContainers
|
||||
/// may also have resistances to certain damage types, defined via a <see cref="ResistanceSetPrototype"/>.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
[Friend(typeof(DamageableSystem))]
|
||||
public class DamageableComponent : Component, IRadiationAct, IExAct
|
||||
{
|
||||
public override string Name => "Damageable";
|
||||
|
||||
/// <summary>
|
||||
/// This <see cref="DamageContainerPrototype"/> specifies what damage types are supported by this component.
|
||||
/// If null, all damage types will be supported.
|
||||
/// </summary>
|
||||
[DataField("damageContainer", customTypeSerializer: typeof(PrototypeIdSerializer<DamageContainerPrototype>))]
|
||||
public string? DamageContainerID;
|
||||
|
||||
/// <summary>
|
||||
/// This <see cref="ResistanceSetPrototype"/> will be applied to any damage that is dealt to this container,
|
||||
/// unless the damage explicitly ignores resistances.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("resistanceSet", customTypeSerializer: typeof(PrototypeIdSerializer<ResistanceSetPrototype>))]
|
||||
public string? ResistanceSetID;
|
||||
|
||||
/// <summary>
|
||||
/// All the damage information is stored in this <see cref="DamageSpecifier"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this data-field is specified, this allows damageable components to be initialized with non-zero damage.
|
||||
/// </remarks>
|
||||
[DataField("damage")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public DamageSpecifier Damage = new();
|
||||
|
||||
/// <summary>
|
||||
/// Damage, indexed by <see cref="DamageGroupPrototype"/> ID keys.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Groups which have no members that are supported by this component will not be present in this
|
||||
/// dictionary.
|
||||
/// </remarks>
|
||||
[ViewVariables] public Dictionary<string, int> DamagePerGroup = new();
|
||||
|
||||
/// <summary>
|
||||
/// The sum of all damages in the DamageableComponent.
|
||||
/// </summary>
|
||||
[ViewVariables] public int TotalDamage;
|
||||
|
||||
// Really these shouldn't be here. OnExplosion() and RadiationAct() should be handled elsewhere.
|
||||
[ViewVariables]
|
||||
[DataField("radiationDamageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string> RadiationDamageTypeIDs = new() {"Radiation"};
|
||||
[ViewVariables]
|
||||
[DataField("explosionDamageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string> ExplosionDamageTypeIDs = new() { "Piercing", "Heat" };
|
||||
|
||||
// TODO RADIATION Remove this.
|
||||
void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
|
||||
{
|
||||
var damageValue = Math.Max((int) (frameTime * radiation.RadsPerSecond), 1);
|
||||
|
||||
// Radiation should really just be a damage group instead of a list of types.
|
||||
DamageSpecifier damage = new();
|
||||
foreach (var typeID in ExplosionDamageTypeIDs)
|
||||
{
|
||||
damage.DamageDict.Add(typeID, damageValue);
|
||||
}
|
||||
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, damage);
|
||||
}
|
||||
|
||||
// TODO EXPLOSION Remove this.
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var damageValue = eventArgs.Severity switch
|
||||
{
|
||||
ExplosionSeverity.Light => 20,
|
||||
ExplosionSeverity.Heavy => 60,
|
||||
ExplosionSeverity.Destruction => 250,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
// Explosion should really just be a damage group instead of a list of types.
|
||||
DamageSpecifier damage = new();
|
||||
foreach (var typeID in ExplosionDamageTypeIDs)
|
||||
{
|
||||
damage.DamageDict.Add(typeID, damageValue);
|
||||
}
|
||||
|
||||
EntitySystem.Get<DamageableSystem>().TryChangeDamage(Owner.Uid, damage);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageableComponentState : ComponentState
|
||||
{
|
||||
public readonly Dictionary<string, int> DamageDict;
|
||||
public readonly string? ResistanceSetID;
|
||||
|
||||
public DamageableComponentState(
|
||||
Dictionary<string, int> damageDict,
|
||||
string? resistanceSetID)
|
||||
{
|
||||
DamageDict = damageDict;
|
||||
ResistanceSetID = resistanceSetID;
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Content.Shared/Damage/DamageableSystem.cs
Normal file
243
Content.Shared/Damage/DamageableSystem.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
{
|
||||
public class DamageableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<DamageableComponent, ComponentInit>(DamageableInit);
|
||||
SubscribeLocalEvent<DamageableComponent, ComponentHandleState>(DamageableHandleState);
|
||||
SubscribeLocalEvent<DamageableComponent, ComponentGetState>(DamageableGetState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a damageable component
|
||||
/// </summary>
|
||||
private void DamageableInit(EntityUid uid, DamageableComponent component, ComponentInit _)
|
||||
{
|
||||
if (component.DamageContainerID != null &&
|
||||
_prototypeManager.TryIndex<DamageContainerPrototype>(component.DamageContainerID,
|
||||
out var damageContainerPrototype))
|
||||
{
|
||||
// Initialize damage dictionary, using the types and groups from the damage
|
||||
// container prototype
|
||||
foreach (var type in damageContainerPrototype.SupportedTypes)
|
||||
{
|
||||
component.Damage.DamageDict.TryAdd(type, 0);
|
||||
}
|
||||
|
||||
foreach (var groupID in damageContainerPrototype.SupportedGroups)
|
||||
{
|
||||
var group = _prototypeManager.Index<DamageGroupPrototype>(groupID);
|
||||
foreach (var type in group.DamageTypes)
|
||||
{
|
||||
component.Damage.DamageDict.TryAdd(type, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No DamageContainerPrototype was given. So we will allow the container to support all damage types
|
||||
foreach (var type in _prototypeManager.EnumeratePrototypes<DamageTypePrototype>())
|
||||
{
|
||||
component.Damage.DamageDict.TryAdd(type.ID, 0);
|
||||
}
|
||||
}
|
||||
|
||||
component.DamagePerGroup = component.Damage.GetDamagePerGroup();
|
||||
component.TotalDamage = component.Damage.Total;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directly sets the damage specifier of a damageable component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful for some unfriendly folk. Also ensures that cached values are updated and that a damage changed
|
||||
/// event is raised.
|
||||
/// </remarks>
|
||||
public void SetDamage(DamageableComponent damageable, DamageSpecifier damage)
|
||||
{
|
||||
damageable.Damage = damage;
|
||||
DamageChanged(damageable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the damage in a DamageableComponent was changed, this function should be called.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This updates cached damage information, flags the component as dirty, and raises a damage changed event.
|
||||
/// The damage changed event is used by other systems, such as damage thresholds.
|
||||
/// </remarks>
|
||||
public void DamageChanged(DamageableComponent component, DamageSpecifier? damageDelta = null)
|
||||
{
|
||||
component.DamagePerGroup = component.Damage.GetDamagePerGroup();
|
||||
component.TotalDamage = component.Damage.Total;
|
||||
component.Dirty();
|
||||
RaiseLocalEvent(component.Owner.Uid, new DamageChangedEvent(component, damageDelta), false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies damage specified via a <see cref="DamageSpecifier"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="DamageSpecifier"/> is effectively just a dictionary of damage types and damage values. This
|
||||
/// function just applies the container's resistances (unless otherwise specified) and then changes the
|
||||
/// stored damage data. Division of group damage into types is managed by <see cref="DamageSpecifier"/>.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// Returns a <see cref="DamageSpecifier"/> with information about the actual damage changes. This will be
|
||||
/// null if the user had no applicable components that can take damage.
|
||||
/// </returns>
|
||||
public DamageSpecifier? TryChangeDamage(EntityUid uid, DamageSpecifier damage, bool ignoreResistances = false)
|
||||
{
|
||||
if (!ComponentManager.TryGetComponent<DamageableComponent>(uid, out var damageable))
|
||||
{
|
||||
// TODO BODY SYSTEM pass damage onto body system
|
||||
return null;
|
||||
}
|
||||
|
||||
if (damage == null)
|
||||
{
|
||||
Logger.Error("Null DamageSpecifier. Probably because a required yaml field was not given.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (damage.Empty)
|
||||
{
|
||||
return damage;
|
||||
}
|
||||
|
||||
// Apply resistances
|
||||
if (!ignoreResistances && damageable.ResistanceSetID != null)
|
||||
{
|
||||
if (_prototypeManager.TryIndex<ResistanceSetPrototype>(damageable.ResistanceSetID, out var resistanceSet))
|
||||
{
|
||||
damage = DamageSpecifier.ApplyResistanceSet(damage, resistanceSet);
|
||||
}
|
||||
|
||||
if (damage.Empty)
|
||||
{
|
||||
return damage;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the current damage, for calculating the difference
|
||||
DamageSpecifier oldDamage = new(damageable.Damage);
|
||||
|
||||
damageable.Damage.ExclusiveAdd(damage);
|
||||
damageable.Damage.ClampMin(0);
|
||||
|
||||
var delta = damageable.Damage - oldDamage;
|
||||
delta.TrimZeros();
|
||||
|
||||
if (!delta.Empty)
|
||||
{
|
||||
DamageChanged(damageable, delta);
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all damage types supported by a <see cref="DamageableComponent"/> to the specified value.
|
||||
/// </summary>
|
||||
/// <remakrs>
|
||||
/// Does nothing If the given damage value is negative.
|
||||
/// </remakrs>
|
||||
public void SetAllDamage(DamageableComponent component, int newValue)
|
||||
{
|
||||
if (newValue < 0)
|
||||
{
|
||||
// invalid value
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var type in component.Damage.DamageDict.Keys)
|
||||
{
|
||||
component.Damage.DamageDict[type] = newValue;
|
||||
}
|
||||
|
||||
// Setting damage does not count as 'dealing' damage, even if it is set to a larger value, so we pass an
|
||||
// empty damage delta.
|
||||
DamageChanged(component, new DamageSpecifier());
|
||||
}
|
||||
|
||||
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new DamageableComponentState(component.Damage.DamageDict, component.ResistanceSetID);
|
||||
}
|
||||
|
||||
private void DamageableHandleState(EntityUid uid, DamageableComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not DamageableComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
component.ResistanceSetID = state.ResistanceSetID;
|
||||
|
||||
// Has the damage actually changed?
|
||||
DamageSpecifier newDamage = new() { DamageDict = state.DamageDict };
|
||||
var delta = component.Damage - newDamage;
|
||||
delta.TrimZeros();
|
||||
|
||||
if (!delta.Empty)
|
||||
{
|
||||
component.Damage = newDamage;
|
||||
DamageChanged(component, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DamageChangedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the component whose damage was changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Given that nearly every component that cares about a change in the damage, needs to know the
|
||||
/// current damage values, directly passing this information prevents a lot of duplicate
|
||||
/// Owner.TryGetComponent() calls.
|
||||
/// </remarks>
|
||||
public readonly DamageableComponent Damageable;
|
||||
|
||||
/// <summary>
|
||||
/// The amount by which the damage has changed. If the damage was set directly to some number, this will be
|
||||
/// null.
|
||||
/// </summary>
|
||||
public readonly DamageSpecifier? DamageDelta;
|
||||
|
||||
/// <summary>
|
||||
/// Was any of the damage change dealing damage, or was it all healing?
|
||||
/// </summary>
|
||||
public readonly bool DamageIncreased = false;
|
||||
|
||||
public DamageChangedEvent(DamageableComponent damageable, DamageSpecifier? damageDelta)
|
||||
{
|
||||
Damageable = damageable;
|
||||
DamageDelta = damageDelta;
|
||||
|
||||
if (DamageDelta == null)
|
||||
return;
|
||||
|
||||
foreach (var damageChange in DamageDelta.DamageDict.Values)
|
||||
{
|
||||
if (damageChange > 0)
|
||||
{
|
||||
DamageIncreased = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Content.Shared/Damage/Prototypes/DamageContainerPrototype.cs
Normal file
40
Content.Shared/Damage/Prototypes/DamageContainerPrototype.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// A damage container which can be used to specify support for various damage types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is effectively just a list of damage types that can be specified in YAML files using both damage types
|
||||
/// and damage groups. Currently this is only used to specify what damage types a <see
|
||||
/// cref="DamageableComponent"/> should support.
|
||||
/// </remarks>
|
||||
[Prototype("damageContainer")]
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageContainerPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[DataField("id", required: true)]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// List of damage groups that are supported by this container.
|
||||
/// </summary>
|
||||
[DataField("supportedGroups", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageGroupPrototype>))]
|
||||
public List<string> SupportedGroups = new();
|
||||
|
||||
/// <summary>
|
||||
/// Partial List of damage types supported by this container. Note that members of the damage groups listed
|
||||
/// in <see cref="SupportedGroups"/> are also supported, but they are not included in this list.
|
||||
/// </summary>
|
||||
[DataField("supportedTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string> SupportedTypes = new();
|
||||
}
|
||||
}
|
||||
26
Content.Shared/Damage/Prototypes/DamageGroupPrototype.cs
Normal file
26
Content.Shared/Damage/Prototypes/DamageGroupPrototype.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Damage.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// A Group of <see cref="DamageTypePrototype"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These groups can be used to specify supported damage types of a <see cref="DamageContainerPrototype"/>, or
|
||||
/// to change/get/set damage in a <see cref="DamageableComponent"/>.
|
||||
/// </remarks>
|
||||
[Prototype("damageGroup")]
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageGroupPrototype : IPrototype
|
||||
{
|
||||
[DataField("id", required: true)] public string ID { get; } = default!;
|
||||
|
||||
[DataField("damageTypes", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string> DamageTypes { get; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Shared.Damage
|
||||
namespace Content.Shared.Damage.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// A single damage type. These types are grouped together in <see cref="DamageGroupPrototype"/>s.
|
||||
31
Content.Shared/Damage/Prototypes/ResistanceSetPrototype.cs
Normal file
31
Content.Shared/Damage/Prototypes/ResistanceSetPrototype.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Prototype of damage resistance sets. Can be applied to <see cref="DamageSpecifier"/> using <see
|
||||
/// cref="DamageSpecifier.ApplyResistanceSet(ResistanceSetPrototype)"/>. This can be done several times as the
|
||||
/// <see cref="DamageSpecifier"/> is passed to it's final target. By default the receiving <see cref="DamageableComponent"/>, will
|
||||
/// also apply it's own <see cref="ResistanceSetPrototype"/>.
|
||||
/// </summary>
|
||||
[Prototype("resistanceSet")]
|
||||
[Serializable, NetSerializable]
|
||||
public class ResistanceSetPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
[DataField("id", required: true)]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[DataField("coefficients", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<float, DamageTypePrototype>))]
|
||||
public Dictionary<string, float> Coefficients = new();
|
||||
|
||||
[DataField("flatReductions", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<float, DamageTypePrototype>))]
|
||||
public Dictionary<string, float> FlatReduction = new();
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage.Resistances
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of resistances used by damageable objects.
|
||||
/// Each <see cref="DamageTypePrototype"/> has a multiplier and flat damage
|
||||
/// reduction value.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class ResistanceSet
|
||||
{
|
||||
|
||||
[ViewVariables]
|
||||
public string? ID { get; } = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<DamageTypePrototype, ResistanceSetSettings> Resistances { get; } = new();
|
||||
|
||||
public ResistanceSet()
|
||||
{
|
||||
}
|
||||
|
||||
public ResistanceSet(ResistanceSetPrototype data)
|
||||
{
|
||||
ID = data.ID;
|
||||
Resistances = data.Resistances;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts input damage with the resistance set values.
|
||||
/// Only applies reduction if the amount is damage (positive), not
|
||||
/// healing (negative).
|
||||
/// </summary>
|
||||
/// <param name="damageType">Type of damage.</param>
|
||||
/// <param name="amount">Incoming amount of damage.</param>
|
||||
public int CalculateDamage(DamageTypePrototype damageType, int amount)
|
||||
{
|
||||
|
||||
// Do nothing if the damage type is not specified in resistance set.
|
||||
if (!Resistances.TryGetValue(damageType, out var resistance))
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
|
||||
if (amount > 0) // Only apply reduction if it's healing, not damage.
|
||||
{
|
||||
amount -= resistance.FlatReduction;
|
||||
|
||||
if (amount <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
amount = (int) Math.Ceiling(amount * resistance.Coefficient);
|
||||
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage.Resistances
|
||||
{
|
||||
/// <summary>
|
||||
/// Prototype for the BodyPart class.
|
||||
/// </summary>
|
||||
[Prototype("resistanceSet")]
|
||||
[Serializable, NetSerializable]
|
||||
public class ResistanceSetPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[ViewVariables]
|
||||
[DataField("id", required: true)]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("coefficients", required: true)]
|
||||
private Dictionary<string, float> coefficients { get; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("flatReductions", required: true)]
|
||||
private Dictionary<string, int> flatReductions { get; } = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<DamageTypePrototype, ResistanceSetSettings> Resistances { get; private set; } = new();
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
foreach (var damageTypeID in coefficients.Keys)
|
||||
{
|
||||
var resolvedDamageType = prototypeManager.Index<DamageTypePrototype>(damageTypeID);
|
||||
Resistances.Add(resolvedDamageType, new ResistanceSetSettings(coefficients[damageTypeID], flatReductions[damageTypeID]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resistance Settings for a specific DamageType. Flat reduction should always be applied before the coefficient.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct ResistanceSetSettings
|
||||
{
|
||||
[ViewVariables] public readonly float Coefficient;
|
||||
[ViewVariables] public readonly int FlatReduction;
|
||||
|
||||
public ResistanceSetSettings(float coefficient, int flatReduction)
|
||||
{
|
||||
Coefficient = coefficient;
|
||||
FlatReduction = flatReduction;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,25 +16,24 @@ namespace Content.Shared.MedicalScanner
|
||||
public class MedicalScannerBoundUserInterfaceState : BoundUserInterfaceState
|
||||
{
|
||||
public readonly EntityUid? Entity;
|
||||
public readonly Dictionary<string, int> DamagePerSupportedGroupID;
|
||||
public readonly Dictionary<string, int> DamagePerTypeID;
|
||||
public readonly IReadOnlyDictionary<string, int> DamagePerGroup;
|
||||
public readonly IReadOnlyDictionary<string, int> DamagePerType;
|
||||
public readonly bool IsScanned;
|
||||
|
||||
public MedicalScannerBoundUserInterfaceState(
|
||||
EntityUid? entity,
|
||||
Dictionary<string, int> damagePerSupportedGroupID,
|
||||
Dictionary<string, int> damagePerTypeID,
|
||||
DamageableComponent? damageable,
|
||||
bool isScanned)
|
||||
{
|
||||
Entity = entity;
|
||||
DamagePerSupportedGroupID = damagePerSupportedGroupID;
|
||||
DamagePerTypeID = damagePerTypeID;
|
||||
DamagePerGroup = damageable?.DamagePerGroup ?? new();
|
||||
DamagePerType = damageable?.Damage?.DamageDict ?? new();
|
||||
IsScanned = isScanned;
|
||||
}
|
||||
|
||||
public bool HasDamage()
|
||||
{
|
||||
return DamagePerSupportedGroupID.Count > 0 || DamagePerTypeID.Count > 0;
|
||||
return DamagePerType.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Linq;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.MobState.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -17,18 +16,20 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Shared.MobState.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IDamageableComponent"/>,
|
||||
/// When attached to an <see cref="DamageableComponent"/>,
|
||||
/// this component will handle critical and death behaviors for mobs.
|
||||
/// Additionally, it handles sending effects to clients
|
||||
/// (such as blur effect for unconsciousness) and managing the health HUD.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IMobStateComponent))]
|
||||
[NetworkedComponent()]
|
||||
public abstract class SharedMobStateComponent : Component, IMobStateComponent, IActionBlocker
|
||||
public class MobStateComponent : Component, IMobStateComponent, IActionBlocker
|
||||
{
|
||||
public override string Name => "MobState";
|
||||
|
||||
/// <summary>
|
||||
/// States that this <see cref="SharedMobStateComponent"/> mapped to
|
||||
/// States that this <see cref="MobStateComponent"/> mapped to
|
||||
/// the amount of damage at which they are triggered.
|
||||
/// A threshold is reached when the total damage of an entity is equal
|
||||
/// to or higher than the int key, but lower than the next threshold.
|
||||
@@ -36,7 +37,7 @@ namespace Content.Shared.MobState.Components
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField("thresholds")]
|
||||
private readonly SortedDictionary<int, IMobState> _lowestToHighestStates = default!;
|
||||
private readonly SortedDictionary<int, IMobState> _lowestToHighestStates = new();
|
||||
|
||||
// TODO Remove Nullability?
|
||||
[ViewVariables]
|
||||
@@ -53,7 +54,13 @@ namespace Content.Shared.MobState.Components
|
||||
|
||||
if (CurrentState != null && CurrentThreshold != null)
|
||||
{
|
||||
UpdateState(null, (CurrentState, CurrentThreshold.Value));
|
||||
// Initialize with given states
|
||||
SetMobState(null, (CurrentState, CurrentThreshold.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Initialize with some amount of damage, defaulting to 0.
|
||||
UpdateState(Owner.GetComponentOrNull<DamageableComponent>()?.TotalDamage ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,29 +95,11 @@ namespace Content.Shared.MobState.Components
|
||||
|
||||
if (state.CurrentThreshold == null)
|
||||
{
|
||||
RemoveState(true);
|
||||
RemoveState();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateState(state.CurrentThreshold.Value, true);
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
if (msg.Damageable.Owner != Owner)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateState(msg.Damageable.TotalDamage);
|
||||
|
||||
break;
|
||||
UpdateState(state.CurrentThreshold.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +125,7 @@ namespace Content.Shared.MobState.Components
|
||||
|
||||
public (IMobState state, int threshold)? GetState(int damage)
|
||||
{
|
||||
foreach (var (threshold, state) in _lowestToHighestStates.Reverse())
|
||||
foreach (var (threshold, state) in _highestToLowestStates)
|
||||
{
|
||||
if (damage >= threshold)
|
||||
{
|
||||
@@ -273,36 +262,32 @@ namespace Content.Shared.MobState.Components
|
||||
return TryGetState(earliestState, out state, out threshold);
|
||||
}
|
||||
|
||||
private void RemoveState(bool syncing = false)
|
||||
private void RemoveState()
|
||||
{
|
||||
var old = CurrentState;
|
||||
CurrentState = null;
|
||||
CurrentThreshold = null;
|
||||
|
||||
UpdateState(old, null);
|
||||
|
||||
if (!syncing)
|
||||
{
|
||||
Dirty();
|
||||
}
|
||||
SetMobState(old, null);
|
||||
}
|
||||
|
||||
public void UpdateState(int damage, bool syncing = false)
|
||||
/// <summary>
|
||||
/// Updates the mob state..
|
||||
/// </summary>
|
||||
public void UpdateState(int damage)
|
||||
{
|
||||
if (!TryGetState(damage, out var newState, out var threshold))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateState(CurrentState, (newState, threshold));
|
||||
|
||||
if (!syncing)
|
||||
{
|
||||
Dirty();
|
||||
}
|
||||
SetMobState(CurrentState, (newState, threshold));
|
||||
}
|
||||
|
||||
private void UpdateState(IMobState? old, (IMobState state, int threshold)? current)
|
||||
/// <summary>
|
||||
/// Sets the mob state and marks the component as dirty.
|
||||
/// </summary>
|
||||
private void SetMobState(IMobState? old, (IMobState state, int threshold)? current)
|
||||
{
|
||||
if (!current.HasValue)
|
||||
{
|
||||
@@ -328,9 +313,10 @@ namespace Content.Shared.MobState.Components
|
||||
state.UpdateState(Owner, threshold);
|
||||
|
||||
var message = new MobStateChangedMessage(this, old, state);
|
||||
|
||||
SendMessage(message);
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, message);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
45
Content.Shared/MobState/EntitySystems/MobStateSystem.cs
Normal file
45
Content.Shared/MobState/EntitySystems/MobStateSystem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.MobState.State;
|
||||
using Content.Shared.Movement;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.MobState.EntitySystems
|
||||
{
|
||||
public class MobStateSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MobStateComponent, StartPullAttemptEvent>(OnStartPullAttempt);
|
||||
SubscribeLocalEvent<MobStateComponent, DamageChangedEvent>(UpdateState);
|
||||
SubscribeLocalEvent<MobStateComponent, MovementAttemptEvent>(OnMoveAttempt);
|
||||
}
|
||||
|
||||
private void OnStartPullAttempt(EntityUid uid, MobStateComponent component, StartPullAttemptEvent args)
|
||||
{
|
||||
if(component.IsIncapacitated())
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
public void UpdateState(EntityUid _, MobStateComponent component, DamageChangedEvent args)
|
||||
{
|
||||
component.UpdateState(args.Damageable.TotalDamage);
|
||||
}
|
||||
|
||||
private void OnMoveAttempt(EntityUid uid, MobStateComponent component, MovementAttemptEvent args)
|
||||
{
|
||||
switch (component.CurrentState)
|
||||
{
|
||||
case SharedCriticalMobState:
|
||||
case SharedDeadMobState:
|
||||
args.Cancel();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.MobState.State;
|
||||
using Content.Shared.Movement;
|
||||
using Content.Shared.Pulling.Events;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.MobState.EntitySystems
|
||||
{
|
||||
public class SharedMobStateSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedMobStateComponent, StartPullAttemptEvent>(OnStartPullAttempt);
|
||||
SubscribeLocalEvent<SharedMobStateComponent, MovementAttemptEvent>(OnMoveAttempt);
|
||||
}
|
||||
|
||||
private void OnStartPullAttempt(EntityUid uid, SharedMobStateComponent component, StartPullAttemptEvent args)
|
||||
{
|
||||
if(component.IsIncapacitated())
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnMoveAttempt(EntityUid uid, SharedMobStateComponent component, MovementAttemptEvent args)
|
||||
{
|
||||
switch (component.CurrentState)
|
||||
{
|
||||
case SharedCriticalMobState:
|
||||
case SharedDeadMobState:
|
||||
args.Cancel();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,6 @@ namespace Content.Shared.MobState
|
||||
[NotNullWhen(true)] out IMobState? state,
|
||||
out int threshold);
|
||||
|
||||
void UpdateState(int damage, bool syncing = false);
|
||||
void UpdateState(int damage);
|
||||
}
|
||||
}
|
||||
|
||||
293
Content.Tests/Shared/DamageTest.cs
Normal file
293
Content.Tests/Shared/DamageTest.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Tests.Shared
|
||||
{
|
||||
// Basic tests of various damage prototypes and classes.
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DamageSpecifier))]
|
||||
[TestOf(typeof(ResistanceSetPrototype))]
|
||||
[TestOf(typeof(DamageGroupPrototype))]
|
||||
public class DamageTest : ContentUnitTest
|
||||
{
|
||||
|
||||
static private Dictionary<string, float> _resistanceCoefficientDict = new()
|
||||
{
|
||||
// "missing" blunt entry
|
||||
{ "Piercing", -2 },// Turn Piercing into Healing
|
||||
{ "Slash", 3 },
|
||||
{ "Radiation", 1.06f }, // Small change, paired with fractional reduction
|
||||
};
|
||||
|
||||
static private Dictionary<string, float> _resistanceReductionDict = new()
|
||||
{
|
||||
{ "Blunt", - 5 },
|
||||
// "missing" piercing entry
|
||||
{ "Slash", 8 },
|
||||
{ "Radiation", 0.5f }, // Fractional adjustment
|
||||
};
|
||||
|
||||
private IPrototypeManager _prototypeManager;
|
||||
|
||||
private DamageSpecifier _damageSpec;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetup()
|
||||
{
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
_prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadString(_damagePrototypes);
|
||||
_prototypeManager.Resync();
|
||||
|
||||
// Create a damage data set
|
||||
_damageSpec = new(_prototypeManager.Index<DamageGroupPrototype>("Brute"), 6);
|
||||
_damageSpec += new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Radiation"), 3);
|
||||
_damageSpec += new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Slash"), -1); // already exists in brute
|
||||
}
|
||||
|
||||
//Check that DamageSpecifier will split groups and can do arithmetic operations
|
||||
[Test]
|
||||
public void DamageSpecifierTest()
|
||||
{
|
||||
// Create a copy of the damage data
|
||||
DamageSpecifier damageSpec = new(_damageSpec);
|
||||
|
||||
// Check that it properly split up the groups into types
|
||||
int damage;
|
||||
Assert.That(damageSpec.Total, Is.EqualTo(8));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Blunt", out damage));
|
||||
Assert.That(damage, Is.EqualTo(2));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Piercing", out damage));
|
||||
Assert.That(damage, Is.EqualTo(2));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Slash", out damage));
|
||||
Assert.That(damage, Is.EqualTo(1));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Radiation", out damage));
|
||||
Assert.That(damage, Is.EqualTo(3));
|
||||
|
||||
// check that integer multiplication works
|
||||
damageSpec = damageSpec * 2;
|
||||
Assert.That(damageSpec.Total, Is.EqualTo(16));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Blunt", out damage));
|
||||
Assert.That(damage, Is.EqualTo(4));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Piercing", out damage));
|
||||
Assert.That(damage, Is.EqualTo(4));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Slash", out damage));
|
||||
Assert.That(damage, Is.EqualTo(2));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Radiation", out damage));
|
||||
Assert.That(damage, Is.EqualTo(6));
|
||||
|
||||
// check that float multiplication works
|
||||
damageSpec = damageSpec * 2.2f;
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Blunt", out damage));
|
||||
Assert.That(damage, Is.EqualTo(9));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Piercing", out damage));
|
||||
Assert.That(damage, Is.EqualTo(9));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Slash", out damage));
|
||||
Assert.That(damage, Is.EqualTo(4));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Radiation", out damage));
|
||||
Assert.That(damage, Is.EqualTo(13));
|
||||
Assert.That(damageSpec.Total, Is.EqualTo(9 + 9 + 4 + 13));
|
||||
|
||||
// check that integer division works
|
||||
damageSpec = damageSpec / 2;
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Blunt", out damage));
|
||||
Assert.That(damage, Is.EqualTo(5));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Piercing", out damage));
|
||||
Assert.That(damage, Is.EqualTo(5));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Slash", out damage));
|
||||
Assert.That(damage, Is.EqualTo(2));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Radiation", out damage));
|
||||
Assert.That(damage, Is.EqualTo(7));
|
||||
|
||||
// check that float division works
|
||||
damageSpec = damageSpec / 2.4f;
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Blunt", out damage));
|
||||
Assert.That(damage, Is.EqualTo(2));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Piercing", out damage));
|
||||
Assert.That(damage, Is.EqualTo(2));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Slash", out damage));
|
||||
Assert.That(damage, Is.EqualTo(1));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Radiation", out damage));
|
||||
Assert.That(damage, Is.EqualTo(3));
|
||||
|
||||
// Lets also test the constructor with damage types and damage groups works properly.
|
||||
damageSpec = new(_prototypeManager.Index<DamageGroupPrototype>("Brute"), 4);
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Blunt", out damage));
|
||||
Assert.That(damage, Is.EqualTo(1));
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Piercing", out damage));
|
||||
Assert.That(damage, Is.EqualTo(2)); // integer rounding. Piercing is defined as last group member in yaml.
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Slash", out damage));
|
||||
Assert.That(damage, Is.EqualTo(1));
|
||||
|
||||
damageSpec = new(_prototypeManager.Index<DamageTypePrototype>("Piercing"), 4);
|
||||
Assert.That(damageSpec.DamageDict.TryGetValue("Piercing", out damage));
|
||||
Assert.That(damage, Is.EqualTo(4));
|
||||
}
|
||||
|
||||
//Check that DamageSpecifier will be properly adjusted by a resistance set
|
||||
[Test]
|
||||
public void ResistanceSetTest()
|
||||
{
|
||||
// Create a copy of the damage data
|
||||
DamageSpecifier damageSpec = 10 * new DamageSpecifier(_damageSpec);
|
||||
|
||||
// Create a resistance set
|
||||
ResistanceSetPrototype resistanceSet = new()
|
||||
{
|
||||
Coefficients = _resistanceCoefficientDict,
|
||||
FlatReduction = _resistanceReductionDict
|
||||
};
|
||||
|
||||
//damage is initially 20 / 20 / 10 / 30
|
||||
//Each time we subtract -5 / 0 / 8 / 0.5
|
||||
//then multiply by 1 / -2 / 3 / 1.06
|
||||
|
||||
// Apply once
|
||||
damageSpec = DamageSpecifier.ApplyResistanceSet(damageSpec, resistanceSet);
|
||||
Assert.That(damageSpec.DamageDict["Blunt"], Is.EqualTo(25));
|
||||
Assert.That(damageSpec.DamageDict["Piercing"], Is.EqualTo(-40)); // became healing
|
||||
Assert.That(damageSpec.DamageDict["Slash"], Is.EqualTo(6));
|
||||
Assert.That(damageSpec.DamageDict["Radiation"], Is.EqualTo(31)); // would be 32 w/o fraction adjustment
|
||||
|
||||
// And again, checking for some other behavior
|
||||
damageSpec = DamageSpecifier.ApplyResistanceSet(damageSpec, resistanceSet);
|
||||
Assert.That(damageSpec.DamageDict["Blunt"], Is.EqualTo(30));
|
||||
Assert.That(damageSpec.DamageDict["Piercing"], Is.EqualTo(-40)); // resistances don't apply to healing
|
||||
Assert.That(!damageSpec.DamageDict.ContainsKey("Slash")); // Reduction reduced to 0, and removed from specifier
|
||||
Assert.That(damageSpec.DamageDict["Radiation"], Is.EqualTo(32));
|
||||
}
|
||||
|
||||
// Default damage Yaml
|
||||
private string _damagePrototypes = @"
|
||||
- type: damageType
|
||||
id: Blunt
|
||||
|
||||
- type: damageType
|
||||
id: Slash
|
||||
|
||||
- type: damageType
|
||||
id: Piercing
|
||||
|
||||
- type: damageType
|
||||
id: Heat
|
||||
|
||||
- type: damageType
|
||||
id: Shock
|
||||
|
||||
- type: damageType
|
||||
id: Cold
|
||||
|
||||
# Poison damage. Generally caused by various reagents being metabolised.
|
||||
- type: damageType
|
||||
id: Poison
|
||||
|
||||
- type: damageType
|
||||
id: Radiation
|
||||
|
||||
# Damage due to being unable to breathe.
|
||||
# Represents not enough oxygen (or equivalent) getting to the blood.
|
||||
# Usually healed automatically if entity can breathe
|
||||
- type: damageType
|
||||
id: Asphyxiation
|
||||
|
||||
# Damage representing not having enough blood.
|
||||
# Represents there not enough blood to supply oxygen (or equivalent).
|
||||
- type: damageType
|
||||
id: Bloodloss
|
||||
|
||||
- type: damageType
|
||||
id: Cellular
|
||||
|
||||
- type: damageGroup
|
||||
id: Brute
|
||||
damageTypes:
|
||||
- Blunt
|
||||
- Slash
|
||||
- Piercing
|
||||
|
||||
- type: damageGroup
|
||||
id: Burn
|
||||
damageTypes:
|
||||
- Heat
|
||||
- Shock
|
||||
- Cold
|
||||
|
||||
# Airloss (sometimes called oxyloss)
|
||||
# Caused by asphyxiation or bloodloss.
|
||||
# Note that most medicine and damaging effects should probably modify either asphyxiation or
|
||||
# bloodloss, not this whole group, unless you have a wonder drug that affects both.
|
||||
- type: damageGroup
|
||||
id: Airloss
|
||||
damageTypes:
|
||||
- Asphyxiation
|
||||
- Bloodloss
|
||||
|
||||
# As with airloss, most medicine and damage effects should probably modify either poison or radiation.
|
||||
# Though there are probably some radioactive poisons.
|
||||
- type: damageGroup
|
||||
id: Toxin
|
||||
damageTypes:
|
||||
- Poison
|
||||
- Radiation
|
||||
|
||||
- type: damageGroup
|
||||
id: Genetic
|
||||
damageTypes:
|
||||
- Cellular
|
||||
|
||||
- type: resistanceSet
|
||||
id: Metallic
|
||||
coefficients:
|
||||
Blunt: 0.7
|
||||
Slash: 0.5
|
||||
Piercing: 0.7
|
||||
Shock: 1.2
|
||||
flatReductions:
|
||||
Blunt: 5
|
||||
|
||||
- type: resistanceSet
|
||||
id: Inflatable
|
||||
coefficients:
|
||||
Blunt: 0.5
|
||||
Piercing: 2.0
|
||||
Heat: 0.5
|
||||
Shock: 0
|
||||
flatReductions:
|
||||
Blunt: 5
|
||||
|
||||
- type: resistanceSet
|
||||
id: Glass
|
||||
coefficients:
|
||||
Blunt: 0.5
|
||||
Slash: 0.5
|
||||
Piercing: 0.5
|
||||
Heat: 0
|
||||
Shock: 0
|
||||
flatReductions:
|
||||
Blunt: 5
|
||||
|
||||
- type: damageContainer
|
||||
id: Biological
|
||||
supportedGroups:
|
||||
- Brute
|
||||
- Burn
|
||||
- Toxin
|
||||
- Airloss
|
||||
- Genetic
|
||||
|
||||
- type: damageContainer
|
||||
id: Inorganic
|
||||
supportedGroups:
|
||||
- Brute
|
||||
supportedTypes:
|
||||
- Heat
|
||||
- Shock
|
||||
";
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,8 @@
|
||||
behaviors:
|
||||
- !type:LungBehavior {}
|
||||
|
||||
# TODO DAMAGE UNITS. Some of these damage effects were scaled up to integers.
|
||||
# Scale back down when damage units are in.
|
||||
- type: entity
|
||||
id: OrganHumanHeart
|
||||
parent: BaseHumanOrgan
|
||||
@@ -124,36 +126,40 @@
|
||||
Arithrazine:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Toxin
|
||||
healthChange: -1
|
||||
- !type:HealthChange
|
||||
damageGroup: Brute
|
||||
healthChange: 0.5
|
||||
damage:
|
||||
groups:
|
||||
Toxin: -2 # -1 Multiplying by 2. pls give damage units
|
||||
Brute: 1 # 0.5
|
||||
Bicaridine:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Brute
|
||||
healthChange: -2
|
||||
damage:
|
||||
groups:
|
||||
Brute: -2
|
||||
Dermaline:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Burn
|
||||
healthChange: -3
|
||||
damage:
|
||||
groups:
|
||||
Burn: -3
|
||||
Dexalin:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Airloss
|
||||
healthChange: -1
|
||||
damage:
|
||||
types:
|
||||
Asphyxiation: -1
|
||||
DexalinPlus:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Airloss
|
||||
healthChange: -3
|
||||
damage:
|
||||
types:
|
||||
Asphyxiation: -3
|
||||
Dylovene:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Toxin
|
||||
healthChange: -1
|
||||
damage:
|
||||
types:
|
||||
Poison: -1
|
||||
Ephedrine:
|
||||
effects:
|
||||
- !type:MovespeedModifier
|
||||
@@ -162,45 +168,45 @@
|
||||
HeartbreakerToxin:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Airloss
|
||||
healthChange: 4
|
||||
damage:
|
||||
types:
|
||||
Asphyxiation: 4
|
||||
Kelotane:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Burn
|
||||
healthChange: -1
|
||||
damage:
|
||||
groups:
|
||||
Burn: -1
|
||||
Lexorin:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Airloss
|
||||
healthChange: 7
|
||||
damage:
|
||||
groups:
|
||||
Airloss: 7
|
||||
Meth:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
healthChange: 2.5
|
||||
damageGroup: Toxin
|
||||
damage:
|
||||
types:
|
||||
Poison: 3 # 2.5
|
||||
- !type:MovespeedModifier
|
||||
walkSpeedModifier: 1.3
|
||||
sprintSpeedModifier: 1.3
|
||||
Omnizine:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
healthChange: -2
|
||||
damageGroup: Burn
|
||||
- !type:HealthChange
|
||||
healthChange: -2
|
||||
damageGroup: Toxin
|
||||
- !type:HealthChange
|
||||
healthChange: -2
|
||||
damageGroup: Airloss
|
||||
- !type:HealthChange
|
||||
healthChange: -2
|
||||
damageGroup: Brute
|
||||
damage:
|
||||
groups:
|
||||
Burn: -3 # -2. w/o damage units did not divide into 3 types
|
||||
Toxin: -2
|
||||
Airloss: -2
|
||||
Brute: -3 # -2. w/o damage units did not divide into 3 types
|
||||
Synaptizine:
|
||||
effects:
|
||||
- !type:HealthChange
|
||||
damageGroup: Toxin
|
||||
healthChange: 0.5
|
||||
damage:
|
||||
types:
|
||||
Poison: 1 # 0.5 pls damage units
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanStomach
|
||||
@@ -261,8 +267,9 @@
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
- !type:HealthChange
|
||||
damageGroup: Toxin
|
||||
healthChange: 1
|
||||
damage:
|
||||
types:
|
||||
Poison: 1
|
||||
JuiceWatermelon:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
@@ -306,8 +313,9 @@
|
||||
- !type:SatiateThirst
|
||||
hydrationFactor: 2
|
||||
- !type:HealthChange
|
||||
damageGroup: Toxin
|
||||
healthChange: 1
|
||||
damage:
|
||||
types:
|
||||
Poison: 1
|
||||
|
||||
- type: entity
|
||||
id: OrganHumanLiver
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user