Damageable Refactor 3: Revenge of the Instamerge (#4524)

* Add DamageType And DamageGroup Prototypes

* Remove DamageTypePrototype Field "name" as its redundant

* Change I/DamageableComponent to use prototypes

* Update DamageContainer, ReisistanceSet and DamageChangeData

* Change Barotrauma Component to use DamageType from DamageSystem

* Update AsteroidRockComponent

* update some more components

* update some more components

* Fix m o r e c o m p o n e n t s and their damageType

* all thats left is bug/missing node hunting then verification.

* push changes

* update submodule

* Merge fixes

* push DGP for example

* update damagecomponent across shared and server

* fix a few bugs

* Fix Merge issues

* Refactor damageablecomponent update (#4406)

* Fixing merge.

I messed up part of the merge. this should fix it?

* Barotrauma now uses prototypeManager

As System.Runtime.CompilerServices also has a [Dependency], I think I had to use the full path [Robust.Shared.IoC.Dependency]

* FlammableComponent now uses prototypeManager

* SuicideCommands now use prototypeManager

* Changed  many files to use prototypeManager to resolve damaege prototypes

Yeah.... prototype references would be very nice. maybe this was all a waste of time.

* Grouping prototypeManager.Index with datafield definitions

This will make it easier to eventually add prototype references

* removed unused variable

* Moved lines around.

Lines now consistent with other TODO PROTOTYPE blocks

* Grouping more prototypeManager.Index with datafield definitions

* Removed unnecessary code

* Added more prototypeManager indexing

These ones weren't pointed out by DrSmug. But I think this is all of them? That or my regex is shit.

* Remove redundant _damage field

* Remove redundant _currentTemperature

* Moved variables down

* Added prototypeManager indexing to TemperatureComponent

* WeaponComponent/System now use ProtptypeManager

And as far as I can tell damageType is required, and therefore should never have been null anyway?

* Make ranged weapon clumsy fire effects datafields

And yes, the order in which the clumsy effects occur is very important.

* Made damage on vital body part loss a datafield

* Renamed several damageGroup variables to group

* Capitalised DamageListToDamageGroup

* Make radiation and explosion damage types datafields

* Renamed _supportedDamageGroupIDs and _supportedDamageTypeIDs

* Fixed mistakes

Frogot to remove prototypeManager index DamageTypeTrigger, and wrong variable visibility in TemperatureComponent

* Added necessary code

Is something tragically wrong?

* MeleeWeapon damageType is not actually required

* Fixing someone else's mistakes

A search comes up with nothing in the yaml files, and its not a required field. So no one uses it? Hopefully?

* Changed and renamed damageTypeToDamageGroup

Previously would incorrectly return the total container damage for each group, not the total in the group

* renaming varitables

* Renamed variable DamageClasses

* Added dictionary converting functions

* Added ID-keyed dictionaries

* Making MedicalScanner use ID dictionaries, instead of prototype dictionaries

Oh oh no. I've been able to avoid UI & networking up until now. I have no Idea what I am doing.

* Fix Medical Scanner

* Summary (required)

The joke here is that this fixes the empty summary.

* Removed DamageableComponent.GetDamageGroup/Type

* Renamed "damage classes" to groups.

* Update ChangeDamage description

* Replaced Heal() with SettAllDamage()

Heal() was just a confusing name,

* More Class -> Group renaming

* Replace Class with Group in yaml files

DamageClassTrigger does not appear in any yaml? only in testing?
DamageTypeTrigger appears only in human.yaml?
HealthChangeMetabolism is Mostly in medicine.yml and one in soad.yaml

Why the hell is Cola metabolizable by plants? Who is pouring cola on their plants!?!?

* Fix _prototypeManager being null errors.

* Changing comments

Where are the prototype references

* MetabolismComponent doesn't give free heals anymore.

* Changes HungerComponent healing.

Previously I think it would actually damage you.  Only did this as I though it was causing the fast healing. Turns out that was just BREATHING.

* Generalised a function in DamageableComponent and moved it to DamageGroupPrototype

previously DamageTypesDictToDamageGroupDict was private to DamageableComponent, but was also quite general (nearly a static function). As this sort of function may be needed by other components using DamageGroupPrototypes in the future, I moved it there as a static function instead.

* modified DamageableComponent.ChangeDamage()

ignoreResistances was renamed to ignoreDamageResistances to make it clearer that it had no effect on healing.

Now uses default argument for ignoreDamageResistances, so when healing you are not forced to specify an argument that does nothing.

Also made some general changes to ignoreResistances()

* Changed class->group and added missing damage type functionality to DamageContainerPrototypes

* Added Comments to damage.yml

* Misc Changes to DamageableComponent

* Differentiated between group support and group applicability

So far, every damage type is a member of one, and only one, damage group. So this change has no real effect so far.

* Added proposed alternative to ChangeDamage()

* fixed error in DamageGroupPrototype

* Changes to DamageableComponent

Lots of changes to comments.
Some variables renamed in IDamageableComponent and DamageableComponent (also required renaming in other files)

Some minor logic changes, mostly for incorrect descirptions of boolean return values.

Also further differentiating between ApplicableGroups and SupportedGroups... if that will ever even matter

* Generalised MedicalScannerComponent

If needed, can print miscellaneous damage types now

* Fixed HealthChangeMetabolism bug

* Changing Comments around

* More questions

* Made Barotrauma default to blunt

* Fix RejuvenateTest.cs

* Comments

* Coments and variable names

* fix some master-merge issues

* Removed redundant fields

* Misc changes for readbility of PR diff

* Consistent naming

* Fixed atmos damage bug

* Removed Ranting

* Fixed Hunger after I broke it

* Fixing Bugs

* Removed stupid question

* Removed more stupid questions

* Fix potential null errors.

* Made boolean return values consistent

Also renamed several functions, to make it clear they return a bool. Docs were also updated.

* Removed IoCManager.InjectDependencies()

* Removed unnecessary 'suffocation' prefix

* Fixed Spelling

Also removed accidentally left in logger call

* Fixed Medical Scanner

* Apply suggestions from code review

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* Changing comments and whitespaces

* Made damage thresholds trigger  datafields required

* So many typos

* Changes to DamageableComponents

Changed documentation in IDamageableComponent

Made testing code more readable.

Relabelled groups as 'Applicable' either 'Fully Supported'

* Removed function and degeneralised

* Update DamageableComponent.cs

Removed unused parameters
Fixed Networking

* Added IoCManager.Resolve

* Now using alternative TryChangeDamage()

* Removed function from DamageGroupPrototype

* Removing comments

* Remove bad if statement?

* Fix damageChanged ordering

* Fix hurt server command

* Changed //TODO PROTOTYPE blocks

Now use PrototypeManager differently. Wherever possible, only retrieve the prototype once.

Also added default damage types to some more datafields

* Update Content.Shared/Damage/Container/DamageContainerPrototype.cs

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* renamed _accumulatedHealth -> _accumulatedDamage and added TODOs

* Another class-> group

* Fix bug in generalisation of damage container prototypes

* Addes Tests to make sure I dont keep adding bugs to my own code.

* Changed Return values when setting

* Removed unused class

* Added more tests, split tests into three files

* Made damage types public and VV read-write-able

* Minor changes to DamageableComponent

Replaced internal use of GetDamagePerType with _damageDict and removed some unnecessary fields

* Fix Suicide, by adding IoC Resolve()

* Fix DamageGroupTrigger bug

* Fix typos in tests

* Change comments./docstrings & spacing

* Merge tests, use test prototypes

Co-authored-by: Leon Friedrich <60421075+leonsfriedrich@users.noreply.github.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>

* Fix merge issues

Co-authored-by: Silver <Silvertorch5@gmail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
Co-authored-by: Leon Friedrich <60421075+leonsfriedrich@users.noreply.github.com>
This commit is contained in:
Leon Friedrich
2021-08-25 03:06:27 +10:00
committed by GitHub
parent 2a8dc0e9c7
commit 32d99eaba9
71 changed files with 2048 additions and 983 deletions

View File

@@ -1,10 +1,13 @@
using System.Text;
using System.Collections.Generic;
using Content.Shared.Damage;
using System.Linq;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@@ -14,6 +17,7 @@ namespace Content.Client.MedicalScanner.UI
{
public readonly Button ScanButton;
private readonly Label _diagnostics;
public MedicalScannerWindow()
{
SetSize = (250, 100);
@@ -51,27 +55,65 @@ namespace Content.Client.MedicalScanner.UI
{
text.Append($"{Loc.GetString("medical-scanner-window-entity-health-text", ("entityName", entity.Name))}\n");
foreach (var (@class, classAmount) in state.DamageClasses)
// Show the total damage
var totalDamage = state.DamagePerTypeID.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();
// 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)
{
text.Append($"\n{Loc.GetString("medical-scanner-window-damage-class-text", ("damageClass", @class), ("amount", classAmount))}");
foreach (var type in @class.ToTypes())
// 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.
var group = IoCManager.Resolve<IPrototypeManager>().Index<DamageGroupPrototype>(damageGroupID);
foreach (var type in group.DamageTypes)
{
if (!state.DamageTypes.TryGetValue(type, out var typeAmount))
if (state.DamagePerTypeID.TryGetValue(type.ID, out var typeAmount))
{
continue;
// 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))
{
shownTypeIDs.Add(type.ID);
text.Append($"\n- {Loc.GetString("medical-scanner-window-damage-type-text", ("damageType", type.ID), ("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-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;
SetSize = (250, 575);
// TODO MEDICALSCANNER resize window based on the length of text / number of damage types?
// Also, maybe add color schemes for specific damage groups?
SetSize = (250, 600);
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Content.Server.Damage;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
@@ -7,6 +7,7 @@ using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Commands
{
@@ -20,7 +21,7 @@ namespace Content.IntegrationTests.Tests.Commands
id: DamageableDummy
components:
- type: Damageable
damagePrototype: biologicalDamageContainer
damageContainer: biologicalDamageContainer
- type: MobState
thresholds:
0: !type:NormalMobState {}
@@ -41,6 +42,7 @@ namespace Content.IntegrationTests.Tests.Commands
mapManager.CreateNewMapEntity(MapId.Nullspace);
var entityManager = IoCManager.Resolve<IEntityManager>();
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var human = entityManager.SpawnEntity("DamageableDummy", MapCoordinates.Nullspace);
@@ -53,7 +55,7 @@ namespace Content.IntegrationTests.Tests.Commands
Assert.That(mobState.IsIncapacitated, Is.False);
// Kill the entity
damageable.ChangeDamage(DamageClass.Brute, 10000000, true);
damageable.TryChangeDamage(prototypeManager.Index<DamageGroupPrototype>("Toxin"), 10000000, true);
// Check that it is dead
Assert.That(mobState.IsAlive, Is.False);

View File

@@ -0,0 +1,121 @@
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);
}
});
}
}
}

View File

@@ -1,10 +1,11 @@
using System;
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
{
@@ -12,18 +13,75 @@ namespace Content.IntegrationTests.Tests.Damageable
[TestOf(typeof(DamageableComponent))]
public class DamageableTest : ContentIntegrationTest
{
private const string DamageableEntityId = "DamageableEntityId";
private const string DamageableEntityId = "TestDamageableEntityId";
private const string Group1Id = "TestGroup1";
private const string Group2Id = "TestGroup2";
private const string Group3Id = "TestGroup3";
private string Prototypes = $@"
# Define some damage groups
- type: damageType
id: TestDamage11
- type: damageType
id: TestDamage21
- type: damageType
id: TestDamage22
- type: damageType
id: TestDamage31
- type: damageType
id: TestDamage32
- type: damageType
id: TestDamage33
# Define damage Groups with 1,2,3 damage types
- type: damageGroup
id: {Group1Id}
damageTypes:
- TestDamage11
- type: damageGroup
id: {Group2Id}
damageTypes:
- TestDamage21
- TestDamage22
- type: damageGroup
id: {Group3Id}
damageTypes:
- TestDamage31
- TestDamage32
- TestDamage33
# 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: damageContainer
id: testSomeDamageContainer
supportedGroups:
- {Group3Id}
supportedTypes:
- TestDamage21
- TestDamage22
private static readonly string Prototypes = $@"
- type: entity
id: {DamageableEntityId}
name: {DamageableEntityId}
components:
- type: Damageable
damageContainer: allDamageContainer";
damageContainer: testSomeDamageContainer
";
/// <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>
[Test]
public async Task TestDamageTypeDamageAndHeal()
public async Task TestDamageableComponents()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
@@ -34,10 +92,15 @@ namespace Content.IntegrationTests.Tests.Damageable
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!;
await server.WaitPost(() =>
{
var mapId = sMapManager.NextMapId();
@@ -46,123 +109,280 @@ namespace Content.IntegrationTests.Tests.Damageable
sDamageableEntity = sEntityManager.SpawnEntity(DamageableEntityId, coordinates);
sDamageableComponent = sDamageableEntity.GetComponent<IDamageableComponent>();
group1 = sPrototypeManager.Index<DamageGroupPrototype>(Group1Id);
group2 = sPrototypeManager.Index<DamageGroupPrototype>(Group2Id);
group3 = sPrototypeManager.Index<DamageGroupPrototype>(Group3Id);
});
await server.WaitRunTicks(5);
await server.WaitAssertion(() =>
{
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
// 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 damageToDeal = 7;
foreach (var type in Enum.GetValues<DamageType>())
// Check that the correct types are supported:
foreach (var group in sPrototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
{
Assert.That(sDamageableComponent.SupportsDamageType(type));
// Damage
Assert.That(sDamageableComponent.ChangeDamage(type, damageToDeal, true), Is.True);
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damageToDeal));
Assert.That(sDamageableComponent.TryGetDamage(type, out var damage), Is.True);
Assert.That(damage, Is.EqualTo(damageToDeal));
// Heal
Assert.That(sDamageableComponent.ChangeDamage(type, -damageToDeal, true), Is.True);
Assert.That(sDamageableComponent.TotalDamage, Is.Zero);
Assert.That(sDamageableComponent.TryGetDamage(type, out damage), Is.True);
Assert.That(damage, Is.Zero);
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);
}
}
}
});
}
[Test]
public async Task TestDamageClassDamageAndHeal()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes
});
await server.WaitIdleAsync();
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group1), Is.False);
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group2), Is.True);
Assert.That(sDamageableComponent.IsFullySupportedDamageGroup(group3), Is.True);
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
IEntity sDamageableEntity;
IDamageableComponent sDamageableComponent = null;
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>();
});
await server.WaitRunTicks(5);
await server.WaitAssertion(() =>
{
// Check that damage works properly if perfectly divisible among group members
int damageToDeal, groupDamage, typeDamage; ;
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
foreach (var @class in Enum.GetValues<DamageClass>())
foreach (var damageGroup in sDamageableComponent.FullySupportedDamageGroups)
{
Assert.That(sDamageableComponent.SupportsDamageClass(@class));
var types = @class.ToTypes();
foreach (var type in types)
{
Assert.That(sDamageableComponent.SupportsDamageType(type));
}
var damageToDeal = types.Count * 5;
var types = damageGroup.DamageTypes;
// Damage
Assert.That(sDamageableComponent.ChangeDamage(@class, damageToDeal, true), Is.True);
damageToDeal = types.Count() * 5;
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, damageToDeal, true), Is.True);
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(damageToDeal));
Assert.That(sDamageableComponent.TryGetDamage(@class, out var classDamage), Is.True);
Assert.That(classDamage, 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 var typeDamage), Is.True);
Assert.That(typeDamage, Is.EqualTo(damageToDeal / types.Count));
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.True);
Assert.That(typeDamage, Is.EqualTo(damageToDeal / types.Count()));
}
// Heal
Assert.That(sDamageableComponent.ChangeDamage(@class, -damageToDeal, true), Is.True);
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, -damageToDeal, true), Is.True);
Assert.That(sDamageableComponent.TotalDamage, Is.Zero);
Assert.That(sDamageableComponent.TryGetDamage(@class, out classDamage), Is.True);
Assert.That(classDamage, Is.Zero);
Assert.That(sDamageableComponent.TryGetDamage(damageGroup, out groupDamage), Is.True);
Assert.That(groupDamage, Is.Zero);
foreach (var type in types)
{
Assert.That(sDamageableComponent.TryGetDamage(type, out var typeDamage), Is.True);
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.True);
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
damageToDeal = types.Count() * 5 - 1;
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, damageToDeal, true), Is.True);
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));
}
// Heal
Assert.That(sDamageableComponent.TryChangeDamage(damageGroup, -damageToDeal, true), Is.True);
Assert.That(sDamageableComponent.TotalDamage, Is.Zero);
Assert.That(sDamageableComponent.TryGetDamage(damageGroup, out groupDamage), Is.True);
Assert.That(groupDamage, Is.Zero);
foreach (var type in types)
{
Assert.That(sDamageableComponent.TryGetDamage(type, out typeDamage), Is.True);
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?
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));
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
// Test preferential healing
damageToDeal = 12;
var damageTypes = group3.DamageTypes.ToArray();
// 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 TotalDamageTest()
public async Task TestGeneralizedDamageableComponent()
{
var server = StartServerDummyTicker(new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes
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();
@@ -171,27 +391,62 @@ namespace Content.IntegrationTests.Tests.Damageable
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(() =>
{
var damageType = DamageClass.Brute;
var damage = 10;
// 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);
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, damage, true));
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(10));
// 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);
var totalTypeDamage = 0;
// Check that the correct damage types are supported
Assert.That(sDamageableComponent.IsSupportedDamageType(SharedDamageType), Is.True);
foreach (var type in damageType.ToTypes())
{
Assert.True(sDamageableComponent.TryGetDamage(type, out var typeDamage));
Assert.That(typeDamage, Is.LessThanOrEqualTo(damage));
// 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));
totalTypeDamage += typeDamage;
}
// 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));
Assert.That(totalTypeDamage, Is.EqualTo(damage));
});
}
}

View File

@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Content.Server.Destructible.Thresholds.Triggers;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
@@ -6,14 +6,15 @@ using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible
{
[TestFixture]
[TestOf(typeof(DamageClassTrigger))]
[TestOf(typeof(DamageGroupTrigger))]
[TestOf(typeof(AndTrigger))]
public class DestructibleDamageClassTest : ContentIntegrationTest
public class DestructibleDamageGroupTest : ContentIntegrationTest
{
[Test]
public async Task AndTest()
@@ -31,6 +32,7 @@ namespace Content.IntegrationTests.Tests.Destructible
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
IEntity sDestructibleEntity;
IDamageableComponent sDamageableComponent = null;
@@ -42,7 +44,7 @@ namespace Content.IntegrationTests.Tests.Destructible
var coordinates = new MapCoordinates(0, 0, mapId);
sMapManager.CreateMap(mapId);
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDamageClassEntityId, coordinates);
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleDamageGroupEntityId, coordinates);
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
});
@@ -56,20 +58,23 @@ namespace Content.IntegrationTests.Tests.Destructible
await server.WaitAssertion(() =>
{
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBrute");
var burnDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBurn");
// Raise brute damage to 5
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 5, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 5, true));
// No thresholds reached yet, the earliest one is at 10 damage
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 5, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 5, true));
// No threshold reached, burn needs to be 10 as well
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
// One threshold reached, brute 10 + burn 10
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
@@ -86,52 +91,52 @@ namespace Content.IntegrationTests.Tests.Destructible
var trigger = (AndTrigger) threshold.Trigger;
Assert.IsInstanceOf<DamageClassTrigger>(trigger.Triggers[0]);
Assert.IsInstanceOf<DamageClassTrigger>(trigger.Triggers[1]);
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[0]);
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[1]);
sThresholdListenerComponent.ThresholdsReached.Clear();
// Raise brute damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Lower brute damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -20, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage back up to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
// 10 brute + 10 burn threshold reached, brute was healed and brought back to its threshold amount and slash stayed the same
// 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));
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal both classes of damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, -20, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, -10, true));
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
// Both classes of damage were healed and then raised again, the threshold should have been reached as triggers once is default false
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
@@ -148,8 +153,8 @@ namespace Content.IntegrationTests.Tests.Destructible
trigger = (AndTrigger) threshold.Trigger;
Assert.IsInstanceOf<DamageClassTrigger>(trigger.Triggers[0]);
Assert.IsInstanceOf<DamageClassTrigger>(trigger.Triggers[1]);
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[0]);
Assert.IsInstanceOf<DamageGroupTrigger>(trigger.Triggers[1]);
sThresholdListenerComponent.ThresholdsReached.Clear();
@@ -157,20 +162,20 @@ namespace Content.IntegrationTests.Tests.Destructible
threshold.TriggersOnce = true;
// Heal brute and burn back to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, -10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, -10, true));
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, -10, true));
// No new thresholds reached from healing
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise brute damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise burn damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Burn, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(burnDamageGroup, 10, true));
// No new thresholds reached as triggers once is set to true and it already triggered before
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);

View File

@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Content.Server.Destructible.Thresholds.Triggers;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
@@ -6,6 +6,7 @@ using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible
@@ -56,20 +57,23 @@ namespace Content.IntegrationTests.Tests.Destructible
await server.WaitAssertion(() =>
{
var bluntDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("TestBlunt");
var slashDamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("TestSlash");
// Raise blunt damage to 5
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 5, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 5, true));
// No thresholds reached yet, the earliest one is at 10 damage
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 5, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 5, true));
// No threshold reached, slash needs to be 10 as well
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
// One threshold reached, blunt 10 + slash 10
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
@@ -92,25 +96,25 @@ namespace Content.IntegrationTests.Tests.Destructible
sThresholdListenerComponent.ThresholdsReached.Clear();
// Raise blunt damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 20
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Lower blunt damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -20, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage back up to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
// 10 blunt + 10 slash threshold reached, blunt was healed and brought back to its threshold amount and slash stayed the same
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
@@ -118,20 +122,20 @@ namespace Content.IntegrationTests.Tests.Destructible
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal both types of damage to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, -20, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, -10, true));
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, -20, true));
// No new thresholds reached, healing should not trigger it
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
// Both types of damage were healed and then raised again, the threshold should have been reached as triggers once is default false
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
@@ -157,20 +161,20 @@ namespace Content.IntegrationTests.Tests.Destructible
threshold.TriggersOnce = true;
// Heal blunt and slash back to 0
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, -10, true));
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, -10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, -10, true));
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, -10, true));
// No new thresholds reached from healing
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise blunt damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
// No new thresholds reached
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Raise slash damage to 10
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Slash, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(slashDamageType, 10, true));
// No new thresholds reached as triggers once is set to true and it already triggered before
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Destructible.Thresholds;
using Content.Server.Destructible.Thresholds.Behaviors;
@@ -8,6 +8,7 @@ using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible
@@ -30,6 +31,7 @@ namespace Content.IntegrationTests.Tests.Destructible
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
IEntity sDestructibleEntity = null;
IDamageableComponent sDamageableComponent = null;
@@ -49,10 +51,11 @@ namespace Content.IntegrationTests.Tests.Destructible
await server.WaitAssertion(() =>
{
var coordinates = sDestructibleEntity.Transform.Coordinates;
var bruteDamageGroup = sPrototypeManager.Index<DamageGroupPrototype>("TestBrute");
Assert.DoesNotThrow(() =>
{
Assert.True(sDamageableComponent.ChangeDamage(DamageClass.Brute, 50, true));
Assert.True(sDamageableComponent.TryChangeDamage(bruteDamageGroup, 50, true));
});
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));

View File

@@ -6,9 +6,93 @@ namespace Content.IntegrationTests.Tests.Destructible
public const string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
public const string DestructibleDestructionEntityId = "DestructibleTestsDestructibleDestructionEntity";
public const string DestructibleDamageTypeEntityId = "DestructibleTestsDestructibleDamageTypeEntity";
public const string DestructibleDamageClassEntityId = "DestructibleTestsDestructibleDamageClassEntity";
public const string DestructibleDamageGroupEntityId = "DestructibleTestsDestructibleDamageGroupEntity";
public static readonly string Prototypes = $@"
- type: damageType
id: TestBlunt
- type: damageType
id: TestSlash
- type: damageType
id: TestPiercing
- type: damageType
id: TestHeat
- type: damageType
id: TestShock
- 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:
- TestBlunt
- TestSlash
- TestPiercing
- type: damageGroup
id: TestBurn
damageTypes:
- TestHeat
- 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}
@@ -18,6 +102,7 @@ namespace Content.IntegrationTests.Tests.Destructible
name: {DestructibleEntityId}
components:
- type: Damageable
damageContainer: TestMetallicDamageContainer
- type: Destructible
thresholds:
- trigger:
@@ -46,6 +131,7 @@ namespace Content.IntegrationTests.Tests.Destructible
name: {DestructibleDestructionEntityId}
components:
- type: Damageable
damageContainer: TestMetallicDamageContainer
- type: Destructible
thresholds:
- trigger:
@@ -69,34 +155,36 @@ namespace Content.IntegrationTests.Tests.Destructible
name: {DestructibleDamageTypeEntityId}
components:
- type: Damageable
damageContainer: TestMetallicDamageContainer
- type: Destructible
thresholds:
- trigger:
!type:AndTrigger
triggers:
- !type:DamageTypeTrigger
type: Blunt
damageType: TestBlunt
damage: 10
- !type:DamageTypeTrigger
type: Slash
damageType: TestSlash
damage: 10
- type: TestThresholdListener
- type: entity
id: {DestructibleDamageClassEntityId}
name: {DestructibleDamageClassEntityId}
id: {DestructibleDamageGroupEntityId}
name: {DestructibleDamageGroupEntityId}
components:
- type: Damageable
damageContainer: TestMetallicDamageContainer
- type: Destructible
thresholds:
- trigger:
!type:AndTrigger
triggers:
- !type:DamageClassTrigger
class: Brute
- !type:DamageGroupTrigger
damageGroup: TestBrute
damage: 10
- !type:DamageClassTrigger
class: Burn
- !type:DamageGroupTrigger
damageGroup: TestBurn
damage: 10
- type: TestThresholdListener";
}

View File

@@ -10,6 +10,7 @@ using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
namespace Content.IntegrationTests.Tests.Destructible
@@ -35,6 +36,7 @@ namespace Content.IntegrationTests.Tests.Destructible
var sEntityManager = server.ResolveDependency<IEntityManager>();
var sMapManager = server.ResolveDependency<IMapManager>();
var sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
IEntity sDestructibleEntity;
IDamageableComponent sDamageableComponent = null;
@@ -62,12 +64,14 @@ namespace Content.IntegrationTests.Tests.Destructible
await server.WaitAssertion(() =>
{
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
var bluntDamageType = sPrototypeManager.Index<DamageTypePrototype>("TestBlunt");
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
// No thresholds reached yet, the earliest one is at 20 damage
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true));
// Only one threshold reached, 20
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
@@ -83,7 +87,7 @@ namespace Content.IntegrationTests.Tests.Destructible
sThresholdListenerComponent.ThresholdsReached.Clear();
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 30, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 30, 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));
@@ -112,16 +116,16 @@ namespace Content.IntegrationTests.Tests.Destructible
sThresholdListenerComponent.ThresholdsReached.Clear();
// Damage for 50 again, up to 100 now
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 50, true));
// No thresholds reached as they weren't healed below the trigger amount
Assert.IsEmpty(sThresholdListenerComponent.ThresholdsReached);
// Heal down to 0
sDamageableComponent.Heal();
// Set damage to 0
sDamageableComponent.TrySetAllDamage(0);
// Damage for 100, up to 100
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 100, true));
Assert.True(sDamageableComponent.TryChangeDamage(bluntDamageType, 100, true));
// Two thresholds reached as damage increased past the previous, 20 and 50
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(2));
@@ -129,25 +133,25 @@ namespace Content.IntegrationTests.Tests.Destructible
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal the entity for 40 damage, down to 60
sDamageableComponent.ChangeDamage(DamageType.Blunt, -40, true);
sDamageableComponent.TryChangeDamage(bluntDamageType, -40, true);
// Thresholds don't work backwards
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
// Damage for 10, up to 70
sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true);
sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true);
// Not enough healing to de-trigger a threshold
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
// Heal by 30, down to 40
sDamageableComponent.ChangeDamage(DamageType.Blunt, -30, true);
sDamageableComponent.TryChangeDamage(bluntDamageType, -30, true);
// Thresholds don't work backwards
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
// Damage up to 50 again
sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true);
sDamageableComponent.TryChangeDamage(bluntDamageType, 10, true);
// The 50 threshold should have triggered again, after being healed
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
@@ -177,10 +181,10 @@ namespace Content.IntegrationTests.Tests.Destructible
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal all damage
sDamageableComponent.Heal();
sDamageableComponent.TrySetAllDamage(0);
// Damage up to 50
sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true);
sDamageableComponent.TryChangeDamage(bluntDamageType, 50, true);
// Check that the total damage matches
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50));
@@ -228,7 +232,7 @@ namespace Content.IntegrationTests.Tests.Destructible
sThresholdListenerComponent.ThresholdsReached.Clear();
// Heal the entity completely
sDamageableComponent.Heal();
sDamageableComponent.TrySetAllDamage(0);
// Check that the entity has 0 damage
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
@@ -241,7 +245,7 @@ namespace Content.IntegrationTests.Tests.Destructible
}
// Damage the entity up to 50 damage again
sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true);
sDamageableComponent.TryChangeDamage(bluntDamageType, 50, true);
// Check that the total damage matches
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50));

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Runtime.CompilerServices;
using Content.Server.Alert;
using Content.Server.Pressure;
@@ -7,6 +7,10 @@ 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
{
@@ -18,6 +22,17 @@ 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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(float airPressure)
{
@@ -40,11 +55,11 @@ namespace Content.Server.Atmos.Components
// Low pressure.
case var p when p <= Atmospherics.WarningLowPressure:
pressure *= lowPressureMultiplier;
if(pressure > Atmospherics.WarningLowPressure)
if (pressure > Atmospherics.WarningLowPressure)
goto default;
damageable.ChangeDamage(DamageType.Blunt, Atmospherics.LowPressureDamage, false, Owner);
// Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
damageable.TryChangeDamage(DamageType, Atmospherics.LowPressureDamage,true);
if (status == null) break;
@@ -66,7 +81,8 @@ namespace Content.Server.Atmos.Components
var damage = (int) MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage);
damageable.ChangeDamage(DamageType.Blunt, damage, false, Owner);
// Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear.
damageable.TryChangeDamage(DamageType, damage,true);
if (status == null) break;

View File

@@ -20,6 +20,8 @@ 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
{
@@ -43,6 +45,18 @@ 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"!;
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
public void Extinguish()
{
if (!OnFire) return;
@@ -92,7 +106,7 @@ namespace Content.Server.Atmos.Components
{
// TODO ATMOS Fire resistance from armor
var damage = Math.Min((int) (FireStacks * 2.5f), 10);
damageable.ChangeDamage(DamageClass.Burn, damage, false);
damageable.TryChangeDamage(DamageType, damage, false);
}
AdjustFireStacks(-0.1f * (_resisting ? 10f : 1f));

View File

@@ -1,4 +1,4 @@
using Content.Shared.Body.Part;
using Content.Shared.Body.Part;
using Content.Shared.Damage;
namespace Content.Server.Body
@@ -10,7 +10,7 @@ namespace Content.Server.Body
}
// TODO BODY: Remove and pretend it never existed
public class BodyDamageChangeParams : DamageChangeParams, IBodyHealthChangeParams
public class BodyDamageChangeParams : IBodyHealthChangeParams
{
public BodyDamageChangeParams(BodyPartType part)
{

View File

@@ -13,6 +13,8 @@ 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;
@@ -34,10 +36,6 @@ namespace Content.Server.Body.Respiratory
private bool _isShivering;
private bool _isSweating;
[ViewVariables(VVAccess.ReadWrite)] [DataField("suffocationDamage")] private int _suffocationDamage = 1;
[ViewVariables(VVAccess.ReadWrite)] [DataField("suffocationDamageRecovery")] private int _suffocationDamageRecovery = 1;
[ViewVariables] [DataField("needsGases")] public Dictionary<Gas, float> NeedsGases { get; set; } = new();
[ViewVariables] [DataField("producesGases")] public Dictionary<Gas, float> ProducesGases { get; set; } = new();
@@ -94,6 +92,22 @@ 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"!;
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
private Dictionary<Gas, float> NeedsAndDeficit(float frameTime)
{
var needs = new Dictionary<Gas, float>(NeedsGases);
@@ -349,7 +363,7 @@ namespace Content.Server.Body.Respiratory
return;
}
damageable.ChangeDamage(DamageType.Asphyxiation, _suffocationDamage, false);
damageable.TryChangeDamage(DamageType, _damage, false);
}
private void StopSuffocation()
@@ -358,7 +372,7 @@ namespace Content.Server.Body.Respiratory
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
{
damageable.ChangeDamage(DamageType.Asphyxiation, -_suffocationDamageRecovery, false);
damageable.TryChangeDamage(DamageType, -_damageRecovery, false);
}
if (Owner.TryGetComponent(out ServerAlertsComponent? alertsComponent))

View File

@@ -17,6 +17,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
namespace Content.Server.Chat.Commands
{
@@ -34,21 +35,22 @@ namespace Content.Server.Chat.Commands
var kind = suicide.Suicide(target, chat);
if (kind != SuicideKind.Special)
{
damageableComponent.SetDamage(kind switch
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
damageableComponent.TrySetDamage(kind switch
{
SuicideKind.Blunt => DamageType.Blunt,
SuicideKind.Slash => DamageType.Slash,
SuicideKind.Piercing => DamageType.Piercing,
SuicideKind.Heat => DamageType.Heat,
SuicideKind.Shock => DamageType.Shock,
SuicideKind.Cold => DamageType.Cold,
SuicideKind.Poison => DamageType.Poison,
SuicideKind.Radiation => DamageType.Radiation,
SuicideKind.Asphyxiation => DamageType.Asphyxiation,
SuicideKind.Bloodloss => DamageType.Bloodloss,
_ => DamageType.Blunt
SuicideKind.Blunt => prototypeManager.Index<DamageTypePrototype>("Blunt"),
SuicideKind.Slash => prototypeManager.Index<DamageTypePrototype>("Slash"),
SuicideKind.Piercing => prototypeManager.Index<DamageTypePrototype>("Piercing"),
SuicideKind.Heat => prototypeManager.Index<DamageTypePrototype>("Heat"),
SuicideKind.Shock => prototypeManager.Index<DamageTypePrototype>("Shock"),
SuicideKind.Cold => prototypeManager.Index<DamageTypePrototype>("Cold"),
SuicideKind.Poison => prototypeManager.Index<DamageTypePrototype>("Poison"),
SuicideKind.Radiation => prototypeManager.Index<DamageTypePrototype>("Radiation"),
SuicideKind.Asphyxiation => prototypeManager.Index<DamageTypePrototype>("Asphyxiation"),
SuicideKind.Bloodloss => prototypeManager.Index<DamageTypePrototype>("Bloodloss"),
_ => prototypeManager.Index<DamageTypePrototype>("Blunt")
},
200, source);
200);
}
}
@@ -117,7 +119,7 @@ namespace Content.Server.Chat.Commands
var selfMessage = Loc.GetString("suicide-command-default-text-self");
owner.PopupMessage(selfMessage);
dmgComponent.SetDamage(DamageType.Piercing, 200, owner);
dmgComponent.TrySetDamage(IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("Piercing"), 200);
// Prevent the player from returning to the body.
// Note that mind cannot be null because otherwise owner would be null.

View File

@@ -5,6 +5,9 @@ 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
{
@@ -12,7 +15,7 @@ namespace Content.Server.Chemistry.ReagentEffects
/// Default metabolism for medicine reagents. Attempts to find a DamageableComponent on the target,
/// and to update its damage values.
/// </summary>
public class HealthChange : ReagentEffect
public class HealthChange : ReagentEffect, ISerializationHooks
{
/// <summary>
/// How much damage is changed when 1u of the reagent is metabolized.
@@ -20,35 +23,45 @@ namespace Content.Server.Chemistry.ReagentEffects
[DataField("healthChange")]
public float AmountToChange { get; set; } = 1.0f;
/// <summary>
/// Class of damage changed, Brute, Burn, Toxin, Airloss.
/// </summary>
[DataField("damageClass")]
public DamageClass DamageType { get; set; } = DamageClass.Brute;
// TODO DAMAGE UNITS When damage units support decimals, get rid of this.
// See also _accumulatedDamage in ThirstComponent and HungerComponent
private float _accumulatedDamage;
private float _accumulatedHealth;
/// <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? health))
if (solutionEntity.TryGetComponent(out IDamageableComponent? damageComponent))
{
health.ChangeDamage(DamageType, (int)AmountToChange, true);
float decHealthChange = (float) (AmountToChange - (int) AmountToChange);
_accumulatedHealth += decHealthChange;
damageComponent.TryChangeDamage(DamageGroup, (int)AmountToChange, true);
if (_accumulatedHealth >= 1)
float decHealthChange = (float) (AmountToChange - (int) AmountToChange);
_accumulatedDamage += decHealthChange;
if (_accumulatedDamage >= 1)
{
health.ChangeDamage(DamageType, 1, true);
_accumulatedHealth -= 1;
damageComponent.TryChangeDamage(DamageGroup, 1, true);
_accumulatedDamage -= 1;
}
else if(_accumulatedHealth <= -1)
else if(_accumulatedDamage <= -1)
{
health.ChangeDamage(DamageType, -1, true);
_accumulatedHealth += 1;
damageComponent.TryChangeDamage(DamageGroup, -1, true);
_accumulatedDamage += 1;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Content.Server.Administration;
using Content.Shared.Administration;
@@ -9,6 +10,7 @@ using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Content.Server.Damage.Commands
{
@@ -19,22 +21,24 @@ namespace Content.Server.Damage.Commands
public string Description => "Ouch";
public string Help => $"Usage: {Command} <type/?> <amount> (<entity uid/_>) (<ignoreResistances>)";
private readonly IPrototypeManager _prototypeManager = default!;
public HurtCommand() {
_prototypeManager = IoCManager.Resolve<IPrototypeManager>();
}
private string DamageTypes()
{
var msg = new StringBuilder();
foreach (var dClass in Enum.GetNames(typeof(DamageClass)))
foreach (var damageGroup in _prototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
{
msg.Append($"\n{dClass}");
var types = Enum.Parse<DamageClass>(dClass).ToTypes();
if (types.Count > 0)
msg.Append($"\n{damageGroup.ID}");
if (damageGroup.DamageTypes.Any())
{
msg.Append(": ");
msg.AppendJoin('|', types);
msg.AppendJoin('|', damageGroup.DamageTypes);
}
}
return $"Damage Types:{msg}";
}
@@ -85,6 +89,8 @@ namespace Content.Server.Damage.Commands
string[] args,
[NotNullWhen(true)] out Damage? func)
{
if (!int.TryParse(args[1], out var amount))
{
shell.WriteLine($"{args[1]} is not a valid damage integer.");
@@ -93,42 +99,42 @@ namespace Content.Server.Damage.Commands
return false;
}
if (Enum.TryParse<DamageClass>(args[0], true, out var damageClass))
if (_prototypeManager.TryIndex<DamageGroupPrototype>(args[0], out var damageGroup))
{
func = (damageable, ignoreResistances) =>
{
if (!damageable.DamageClasses.ContainsKey(damageClass))
if (!damageable.ApplicableDamageGroups.Contains(damageGroup))
{
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage class {damageClass}");
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage group {damageGroup}");
return;
}
if (!damageable.ChangeDamage(damageClass, amount, ignoreResistances))
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} {damageClass} damage{(ignoreResistances ? ", ignoring resistances." : ".")}");
shell.WriteLine($"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageGroup} damage{(ignoreResistances ? ", ignoring resistances." : ".")}");
};
return true;
}
// Fall back to DamageType
else if (Enum.TryParse<DamageType>(args[0], true, out var damageType))
else if (_prototypeManager.TryIndex<DamageTypePrototype>(args[0], out var damageType))
{
func = (damageable, ignoreResistances) =>
{
if (!damageable.DamageTypes.ContainsKey(damageType))
if (!damageable.IsSupportedDamageType(damageType))
{
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage class {damageType}");
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} can not be damaged with damage type {damageType}");
return;
}
if (!damageable.ChangeDamage(damageType, amount, ignoreResistances))
if (!damageable.TryChangeDamage(damageType, amount, ignoreResistances))
{
shell.WriteLine($"Entity {damageable.Owner.Name} with id {damageable.Owner.Uid} received no damage.");
@@ -136,9 +142,10 @@ namespace Content.Server.Damage.Commands
}
shell.WriteLine($"Damaged entity {damageable.Owner.Name} with id {damageable.Owner.Uid} for {amount} {damageType} damage{(ignoreResistances ? ", ignoring resistances." : ".")}");
};
};
return true;
}
else
{

View File

@@ -3,6 +3,9 @@ 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
{
@@ -14,8 +17,6 @@ namespace Content.Server.Damage.Components
{
public override string Name => "DamageOnHighSpeedImpact";
[DataField("damage")]
public DamageType Damage { get; set; } = DamageType.Blunt;
[DataField("minimumSpeed")]
public float MinimumSpeed { get; set; } = 20f;
[DataField("baseDamage")]
@@ -34,5 +35,17 @@ namespace Content.Server.Damage.Components
public float DamageCooldown { get; set; } = 2f;
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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
}
}

View File

@@ -3,6 +3,9 @@ using Content.Shared.Damage.Components;
using Content.Shared.Throwing;
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
{
@@ -11,20 +14,31 @@ namespace Content.Server.Damage.Components
{
public override string Name => "DamageOnLand";
[DataField("damageType")]
private DamageType _damageType = DamageType.Blunt;
[DataField("amount")]
[ViewVariables(VVAccess.ReadWrite)]
private int _amount = 1;
[DataField("ignoreResistances")]
[ViewVariables(VVAccess.ReadWrite)]
private 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")]
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);
}
void ILand.Land(LandEventArgs eventArgs)
{
if (!Owner.TryGetComponent(out IDamageableComponent? damageable)) return;
damageable.ChangeDamage(_damageType, _amount, _ignoreResistances, eventArgs.User);
if (!Owner.TryGetComponent(out IDamageableComponent? damageable))
return;
damageable.TryChangeDamage(DamageType, _amount, _ignoreResistances);
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Server.Tools.Components;
using Content.Shared.Damage;
@@ -7,12 +7,16 @@ 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
{
[RegisterComponent]
public class DamageOnToolInteractComponent : Component, IInteractUsing
{
public override string Name => "DamageOnToolInteract";
[DataField("damage")]
@@ -21,6 +25,23 @@ namespace Content.Server.Damage.Components
[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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype WeldingDamageType = default!;
[DataField("defaultDamageType")]
private readonly string _defaultDamageTypeID = "Blunt";
[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);
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
@@ -44,17 +65,15 @@ namespace Content.Server.Damage.Components
protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool)
{
if (eventArgs.Target.TryGetComponent<IDamageableComponent>(out var damageable))
{
damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding)
? DamageType.Heat
: DamageType.Blunt,
Damage, false, eventArgs.User);
if (!eventArgs.Target.TryGetComponent<IDamageableComponent>(out var damageable))
return false;
return true;
}
damageable.TryChangeDamage(tool.HasQuality(ToolQuality.Welding)
? WeldingDamageType
: DefaultDamageType,
Damage);
return false;
return true;
}
}
}

View File

@@ -2,6 +2,8 @@ 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;
namespace Content.Server.Damage.Components
{
@@ -11,13 +13,21 @@ namespace Content.Server.Damage.Components
{
public override string Name => "DamageOtherOnHit";
[DataField("damageType")]
public DamageType DamageType { get; } = DamageType.Blunt;
[DataField("amount")]
public int Amount { get; } = 1;
[DataField("ignoreResistances")]
public bool IgnoreResistances { get; } = 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 = "Blunt";
public DamageTypePrototype DamageType { get; set; } = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
}
}

View File

@@ -46,7 +46,7 @@ namespace Content.Server.Damage
if (ComponentManager.TryGetComponent(uid, out StunnableComponent? stun) && _robustRandom.Prob(component.StunChance))
stun.Stun(component.StunSeconds);
damageable.ChangeDamage(component.Damage, damage, false, args.OtherFixture.Body.Owner);
damageable.TryChangeDamage(component.DamageType, damage);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Content.Server.Damage
if (!args.Target.TryGetComponent(out IDamageableComponent? damageable))
return;
damageable.ChangeDamage(component.DamageType, component.Amount, component.IgnoreResistances, args.User);
damageable.TryChangeDamage(component.DamageType, component.Amount, component.IgnoreResistances);
}
}
}

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic;
using Content.Server.Atmos.Components;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Resistances;
using Content.Shared.GameTicking;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
@@ -43,8 +42,9 @@ namespace Content.Server.Damage
if (entity.TryGetComponent(out IDamageableComponent? damageable))
{
damageable.SupportedTypes.Clear();
damageable.SupportedClasses.Clear();
damageable.SupportedDamageTypes.Clear();
damageable.FullySupportedDamageGroups.Clear();
damageable.ApplicableDamageGroups.Clear();
}
return true;
@@ -69,14 +69,19 @@ namespace Content.Server.Damage
if (entity.TryGetComponent(out IDamageableComponent? damageable))
{
if (old.SupportedTypes != null)
if (old.SupportedDamageTypes != null)
{
damageable.SupportedTypes.UnionWith(old.SupportedTypes);
damageable.SupportedDamageTypes.UnionWith(old.SupportedDamageTypes);
}
if (old.SupportedClasses != null)
if (old.SupportedDamageGroups != null)
{
damageable.SupportedClasses.UnionWith(old.SupportedClasses);
damageable.FullySupportedDamageGroups.UnionWith(old.SupportedDamageGroups);
}
if (old.ApplicableDamageGroups != null)
{
damageable.ApplicableDamageGroups.UnionWith(old.ApplicableDamageGroups);
}
}
@@ -111,8 +116,9 @@ namespace Content.Server.Damage
if (entity.TryGetComponent(out IDamageableComponent? damageable))
{
SupportedTypes = damageable.SupportedTypes.ToHashSet();
SupportedClasses = damageable.SupportedClasses.ToHashSet();
SupportedDamageTypes = damageable.SupportedDamageTypes.ToHashSet();
SupportedDamageGroups = damageable.FullySupportedDamageGroups.ToHashSet();
ApplicableDamageGroups = damageable.ApplicableDamageGroups.ToHashSet();
}
}
@@ -120,9 +126,11 @@ namespace Content.Server.Damage
public bool MovedByPressure { get; }
public HashSet<DamageType>? SupportedTypes { get; }
public HashSet<DamageTypePrototype>? SupportedDamageTypes { get; }
public HashSet<DamageClass>? SupportedClasses { get; }
public HashSet<DamageGroupPrototype>? SupportedDamageGroups { get; }
public HashSet<DamageGroupPrototype>? ApplicableDamageGroups { get; }
}
}
}

View File

@@ -61,7 +61,7 @@ namespace Content.Server.Damage
{
if (target.TryGetComponent(out IDamageableComponent? damage))
{
damage.Heal();
damage.TrySetAllDamage(0);
}
if (target.TryGetComponent(out IMobStateComponent? mobState))

View File

@@ -1,39 +0,0 @@
using System;
using Content.Shared.Damage;
using Content.Shared.Damage.Components;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Destructible.Thresholds.Triggers
{
/// <summary>
/// A trigger that will activate when the amount of damage received
/// of the specified class is above the specified threshold.
/// </summary>
[Serializable]
[DataDefinition]
public class DamageClassTrigger : IThresholdTrigger
{
/// <summary>
/// The class to check the damage of.
/// </summary>
[DataField("class")]
public DamageClass? Class { get; set; }
/// <summary>
/// The amount of damage at which this threshold will trigger.
/// </summary>
[DataField("damage")]
public int Damage { get; set; }
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{
if (Class == null)
{
return false;
}
return damageable.TryGetDamage(Class.Value, out var damageReceived) &&
damageReceived >= Damage;
}
}
}

View File

@@ -0,0 +1,42 @@
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;
namespace Content.Server.Destructible.Thresholds.Triggers
{
/// <summary>
/// A trigger that will activate when the amount of damage received
/// of the specified class is above the specified threshold.
/// </summary>
[Serializable]
[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);
/// <summary>
/// The amount of damage at which this threshold will trigger.
/// </summary>
[DataField("damage", required: true)]
public int Damage { get; set; } = default!;
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{
if (DamageGroup == null)
{
return false;
}
return damageable.TryGetDamage(DamageGroup, out var damageReceived) &&
damageReceived >= Damage;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Content.Shared.Damage.Components;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -15,8 +15,8 @@ namespace Content.Server.Destructible.Thresholds.Triggers
/// <summary>
/// The amount of damage at which this threshold will trigger.
/// </summary>
[DataField("damage")]
public int Damage { get; set; }
[DataField("damage", required: true)]
public int Damage { get; set; } = default!;
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{

View File

@@ -1,7 +1,9 @@
using System;
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;
namespace Content.Server.Destructible.Thresholds.Triggers
{
@@ -13,20 +15,24 @@ namespace Content.Server.Destructible.Thresholds.Triggers
[DataDefinition]
public class DamageTypeTrigger : IThresholdTrigger
{
[DataField("type")]
public DamageType? Type { get; set; }
// 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("damage")]
public int Damage { get; set; }
[DataField("damage", required: true)]
public int Damage { get; set; } = default!;
public bool Reached(IDamageableComponent damageable, DestructibleSystem system)
{
if (Type == null)
if (DamageType == null)
{
return false;
}
return damageable.TryGetDamage(Type.Value, out var damageReceived) &&
return damageable.TryGetDamage(DamageType, out var damageReceived) &&
damageReceived >= Damage;
}
}

View File

@@ -30,6 +30,8 @@ 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
{
@@ -45,6 +47,18 @@ 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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
public override DoorState State
{
get => base.State;
@@ -536,7 +550,7 @@ namespace Content.Server.Doors.Components
hitsomebody = true;
CurrentlyCrushing.Add(e.Owner.Uid);
damage.ChangeDamage(DamageType.Blunt, DoorCrushDamage, false, Owner);
damage.TryChangeDamage(DamageType, DoorCrushDamage);
stun.Paralyze(DoorStunTime);
}

View File

@@ -10,6 +10,7 @@ using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Presets
{
@@ -66,7 +67,8 @@ namespace Content.Server.GameTicking.Presets
if (playerEntity.TryGetComponent(out IDamageableComponent? damageable))
{
//todo: what if they dont breathe lol
damageable.SetDamage(DamageType.Asphyxiation, 200, playerEntity);
//cry deeply
damageable.TrySetDamage(IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>("Asphyxiation"), 200);
}
}
}

View File

@@ -23,6 +23,7 @@ using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -38,6 +39,7 @@ namespace Content.Server.GameTicking.Presets
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public string PDAPrototypeName => "CaptainPDA";
public string BeltPrototypeName => "ClothingBeltJanitorFilled";
@@ -192,11 +194,11 @@ namespace Content.Server.GameTicking.Presets
{
if (mobState.IsCritical())
{
// TODO: This is copy/pasted from ghost code. Really, IDamagableComponent needs a method to reliably kill the target.
// 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.ChangeDamage(DamageType.Asphyxiation, 100, true);
damageable.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), 100, true);
}
}
else if (!mobState.IsDead())

View File

@@ -20,6 +20,7 @@ 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.Player;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -77,6 +78,19 @@ 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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
_lightBulbContainer = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, "light_bulb");
}
[ViewVariables]
public LightBulbComponent? LightBulb
{
@@ -126,7 +140,7 @@ namespace Content.Server.Light.Components
void Burn()
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("powered-light-component-burn-hand"));
damageableComponent.ChangeDamage(DamageType.Heat, 20, false, Owner);
damageableComponent.TryChangeDamage(DamageType, 20);
SoundSystem.Play(Filter.Pvs(Owner), _burnHandSound.GetSound(), Owner);
}
@@ -249,13 +263,6 @@ namespace Content.Server.Light.Components
}
}
protected override void Initialize()
{
base.Initialize();
_lightBulbContainer = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, "light_bulb");
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);

View File

@@ -1,15 +1,17 @@
using System.Collections.Generic;
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.Events;
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
{
@@ -18,7 +20,12 @@ namespace Content.Server.Medical.Components
{
public override string Name => "Healing";
[DataField("heal")] public Dictionary<DamageType, int> Heal { get; private set; } = new();
// 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 )]
[ViewVariables(VVAccess.ReadWrite)]
public Dictionary<string, int> Heal = new();
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
@@ -48,9 +55,9 @@ namespace Content.Server.Medical.Components
return true;
}
foreach (var (type, amount) in Heal)
foreach (var (damageTypeID, amount) in Heal)
{
damageable.ChangeDamage(type, -amount, true);
damageable.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(damageTypeID), -amount, true);
}
return true;

View File

@@ -99,8 +99,8 @@ namespace Content.Server.Medical.Components
private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState =
new(
null,
new Dictionary<DamageClass, int>(),
new Dictionary<DamageType, int>(),
new Dictionary<string, int>(),
new Dictionary<string, int>(),
false);
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
@@ -121,12 +121,13 @@ namespace Content.Server.Medical.Components
return EmptyUIState;
}
var classes = new Dictionary<DamageClass, int>(damageable.DamageClasses);
var types = new Dictionary<DamageType, int>(damageable.DamageTypes);
// 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, classes, types, true);
return new MedicalScannerBoundUserInterfaceState(body.Uid, groups, types, true);
}
var cloningSystem = EntitySystem.Get<CloningSystem>();
@@ -134,7 +135,7 @@ namespace Content.Server.Medical.Components
mindComponent.Mind != null &&
cloningSystem.HasDnaScan(mindComponent.Mind);
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types, scanned);
return new MedicalScannerBoundUserInterfaceState(body.Uid, groups, types, scanned);
}
private void UpdateUserInterface()

View File

@@ -7,9 +7,12 @@ using Content.Shared.Mining;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Mining.Components
{
@@ -21,10 +24,16 @@ 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));
@@ -37,7 +46,7 @@ namespace Content.Server.Mining.Components
if (!item.TryGetComponent(out MeleeWeaponComponent? meleeWeaponComponent))
return false;
Owner.GetComponent<IDamageableComponent>().ChangeDamage(DamageType.Blunt, meleeWeaponComponent.Damage, false, item);
Owner.GetComponent<IDamageableComponent>().TryChangeDamage(DamageType, meleeWeaponComponent.Damage);
if (!item.TryGetComponent(out PickaxeComponent? pickaxeComponent))
return true;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Content.Server.Alert;
using Content.Shared.Alert;
@@ -14,6 +14,7 @@ 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
{
@@ -22,6 +23,10 @@ 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;
// Base stuff
[ViewVariables(VVAccess.ReadWrite)]
public float BaseDecayRate
@@ -29,7 +34,7 @@ namespace Content.Server.Nutrition.Components
get => _baseDecayRate;
set => _baseDecayRate = value;
}
[DataField("base_decay_rate")]
[DataField("baseDecayRate")]
private float _baseDecayRate = 0.1f;
[ViewVariables(VVAccess.ReadWrite)]
@@ -59,11 +64,11 @@ namespace Content.Server.Nutrition.Components
public Dictionary<HungerThreshold, float> HungerThresholds => _hungerThresholds;
private readonly Dictionary<HungerThreshold, float> _hungerThresholds = new()
{
{HungerThreshold.Overfed, 600.0f},
{HungerThreshold.Okay, 450.0f},
{HungerThreshold.Peckish, 300.0f},
{HungerThreshold.Starving, 150.0f},
{HungerThreshold.Dead, 0.0f},
{ HungerThreshold.Overfed, 600.0f },
{ HungerThreshold.Okay, 450.0f },
{ HungerThreshold.Peckish, 300.0f },
{ HungerThreshold.Starving, 150.0f },
{ HungerThreshold.Dead, 0.0f },
};
public static readonly Dictionary<HungerThreshold, AlertType> HungerThresholdAlertTypes = new()
@@ -73,6 +78,18 @@ 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"!;
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
public void HungerThresholdEffect(bool force = false)
{
if (_currentHungerThreshold != _lastHungerThreshold || force)
@@ -177,6 +194,7 @@ namespace Content.Server.Nutrition.Components
if (_currentHungerThreshold != HungerThreshold.Dead)
return;
// --> Current Hunger is below dead threshold
if (!Owner.TryGetComponent(out IDamageableComponent? damageable))
return;
@@ -186,7 +204,14 @@ namespace Content.Server.Nutrition.Components
if (!mobState.IsDead())
{
damageable.ChangeDamage(DamageType.Blunt, 2, true);
// --> 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);
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Content.Server.Alert;
using Content.Shared.Alert;
@@ -14,6 +14,7 @@ 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
{
@@ -22,6 +23,10 @@ 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;
// Base stuff
[ViewVariables(VVAccess.ReadWrite)]
public float BaseDecayRate
@@ -29,7 +34,7 @@ namespace Content.Server.Nutrition.Components
get => _baseDecayRate;
set => _baseDecayRate = value;
}
[DataField("base_decay_rate")]
[DataField("baseDecayRate")]
private float _baseDecayRate = 0.1f;
[ViewVariables(VVAccess.ReadWrite)]
@@ -72,6 +77,18 @@ 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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
public void ThirstThresholdEffect(bool force = false)
{
if (_currentThirstThreshold != _lastThirstThreshold || force)
@@ -174,6 +191,7 @@ namespace Content.Server.Nutrition.Components
if (_currentThirstThreshold != ThirstThreshold.Dead)
return;
// --> Current Hunger is below dead threshold
if (!Owner.TryGetComponent(out IDamageableComponent? damageable))
return;
@@ -183,7 +201,15 @@ namespace Content.Server.Nutrition.Components
if (!mobState.IsDead())
{
damageable.ChangeDamage(DamageType.Blunt, 2, true);
// --> 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);
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Content.Server.Nutrition.EntitySystems
{
comp.OnUpdate(_accumulatedFrameTime);
}
_accumulatedFrameTime -= 1;
_accumulatedFrameTime = 0;
}
}
}

View File

@@ -9,8 +9,10 @@ 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;
namespace Content.Server.Projectiles.Components
{
@@ -25,26 +27,18 @@ 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;
public float Damage
{
get => _damage;
set => _damage = value;
}
[DataField("damage")]
private float _damage = 10f;
public DamageType DamageType => _damageType;
[DataField("damageType")]
private DamageType _damageType = DamageType.Heat;
public float MaxLength => 20.0f;
public float Damage { get; set; } = 10f;
public float MaxLength => 20.0f;
private TimeSpan _startTime;
private TimeSpan _deathTime;
public float ColorModifier { get; set; } = 1.0f;
[DataField("spriteName")]
[DataField("spriteName")]
private string _spriteName = "Objects/Weapons/Guns/Projectiles/laser.png";
[DataField("muzzleFlash")]
private string? _muzzleFlash;
@@ -53,6 +47,19 @@ 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>();

View File

@@ -13,14 +13,12 @@ namespace Content.Server.Projectiles.Components
[ComponentReference(typeof(SharedProjectileComponent))]
public class ProjectileComponent : SharedProjectileComponent
{
[DataField("damages")] private Dictionary<DamageType, int> _damages = new();
[ViewVariables]
public Dictionary<DamageType, int> Damages
{
get => _damages;
set => _damages = value;
}
// 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")]
[ViewVariables(VVAccess.ReadWrite)]
public Dictionary<string, int> Damages { get; set; } = new();
[DataField("deleteOnCollide")]
public bool DeleteOnCollide { get; } = true;

View File

@@ -7,12 +7,17 @@ using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
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!;
public override void Initialize()
{
base.Initialize();
@@ -49,9 +54,9 @@ namespace Content.Server.Projectiles
{
EntityManager.TryGetEntity(component.Shooter, out var shooter);
foreach (var (damageType, amount) in component.Damages)
foreach (var (damageTypeID, amount) in component.Damages)
{
damage.ChangeDamage(damageType, amount, false, shooter);
damage.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(damageTypeID), amount);
}
component.DamagedEntity = true;

View File

@@ -36,7 +36,7 @@ namespace Content.Server.Repairable
{
if (!await welder.UseTool(eventArgs.User, Owner, _doAfterDelay, ToolQuality.Welding, _fuelCost))
return false;
damageable.Heal();
damageable.TrySetAllDamage(0);
Owner.PopupMessage(eventArgs.User,
Loc.GetString("comp-repairable-repair",

View File

@@ -18,7 +18,7 @@ namespace Content.Server.Spawners.Components
[DataField("job_id")]
private string? _jobId;
[field: ViewVariables(VVAccess.ReadWrite)]
[ViewVariables(VVAccess.ReadWrite)]
[DataField("spawn_type")]
public SpawnPointType SpawnType { get; } = SpawnPointType.Unset;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Content.Server.Alert;
using Content.Shared.Alert;
using Content.Shared.Atmos;
@@ -8,6 +8,8 @@ using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Content.Server.Temperature.Components
{
@@ -22,11 +24,21 @@ namespace Content.Server.Temperature.Components
/// <inheritdoc />
public override string Name => "Temperature";
[ViewVariables] public float CurrentTemperature { get => _currentTemperature; set => _currentTemperature = value; }
[DataField("heatDamageThreshold")]
private float _heatDamageThreshold = default;
[DataField("coldDamageThreshold")]
private float _coldDamageThreshold = default;
[DataField("tempDamageCoefficient")]
private float _tempDamageCoefficient = 1;
[DataField("currentTemperature")]
public float CurrentTemperature { get; set; } = Atmospherics.T20C;
[DataField("specificHeat")]
private float _specificHeat = Atmospherics.MinimumHeatCapacity;
[ViewVariables] public float HeatDamageThreshold => _heatDamageThreshold;
[ViewVariables] public float ColdDamageThreshold => _coldDamageThreshold;
[ViewVariables] public float TempDamageCoefficient => _tempDamageCoefficient;
[ViewVariables] public float SpecificHeat => _specificHeat;
[ViewVariables] public float HeatCapacity {
get
{
@@ -39,33 +51,25 @@ namespace Content.Server.Temperature.Components
}
}
[ViewVariables] public float SpecificHeat => _specificHeat;
[DataField("heatDamageThreshold")]
private float _heatDamageThreshold = default;
[DataField("coldDamageThreshold")]
private float _coldDamageThreshold = default;
[DataField("tempDamageCoefficient")]
private float _tempDamageCoefficient = 1;
[DataField("currentTemperature")]
private float _currentTemperature = Atmospherics.T20C;
[DataField("specificHeat")]
private float _specificHeat = Atmospherics.MinimumHeatCapacity;
// 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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype ColdDamageType = default!;
[DataField("hotDamageType")]
private readonly string _hotDamageTypeID = "Heat";
[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 void Update()
{
var tempDamage = 0;
DamageType? damageType = null;
if (CurrentTemperature >= _heatDamageThreshold)
{
tempDamage = (int) Math.Floor((CurrentTemperature - _heatDamageThreshold) * _tempDamageCoefficient);
damageType = DamageType.Heat;
}
else if (CurrentTemperature <= _coldDamageThreshold)
{
tempDamage = (int) Math.Floor((_coldDamageThreshold - CurrentTemperature) * _tempDamageCoefficient);
damageType = DamageType.Cold;
}
if (Owner.TryGetComponent(out ServerAlertsComponent? status))
{
@@ -108,10 +112,19 @@ namespace Content.Server.Temperature.Components
}
}
if (!damageType.HasValue) return;
if (!Owner.TryGetComponent(out IDamageableComponent? component)) return;
component.ChangeDamage(damageType.Value, tempDamage, false);
if (CurrentTemperature >= _heatDamageThreshold)
{
int tempDamage = (int) Math.Floor((CurrentTemperature - _heatDamageThreshold) * _tempDamageCoefficient);
component.TryChangeDamage(HotDamageType, tempDamage, false);
}
else if (CurrentTemperature <= _coldDamageThreshold)
{
int tempDamage = (int) Math.Floor((_coldDamageThreshold - CurrentTemperature) * _tempDamageCoefficient);
component.TryChangeDamage(ColdDamageType, tempDamage, false);
}
}
/// <summary>

View File

@@ -4,6 +4,8 @@ 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
{
@@ -48,15 +50,23 @@ namespace Content.Server.Weapon.Melee.Components
[DataField("damage")]
public int Damage { get; set; } = 5;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("damageType")]
public DamageType DamageType { get; set; } = DamageType.Blunt;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("clickAttackEffect")]
public bool ClickAttackEffect { get; set; } = true;
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";
[ViewVariables(VVAccess.ReadWrite)]
public DamageTypePrototype DamageType = default!;
protected override void Initialize()
{
base.Initialize();
DamageType = IoCManager.Resolve<IPrototypeManager>().Index<DamageTypePrototype>(_damageTypeID);
}
}
}

View File

@@ -25,6 +25,8 @@ namespace Content.Server.Weapon.Melee
public sealed class MeleeWeaponSystem : EntitySystem
{
[Dependency] private IGameTiming _gameTiming = default!;
public override void Initialize()
{
base.Initialize();
@@ -88,7 +90,7 @@ namespace Content.Server.Weapon.Melee
if (target.TryGetComponent(out IDamageableComponent? damageableComponent))
{
damageableComponent.ChangeDamage(comp.DamageType, comp.Damage, false, owner);
damageableComponent.TryChangeDamage(comp.DamageType, comp.Damage);
}
SoundSystem.Play(Filter.Pvs(owner), comp.HitSound.GetSound(), target);
@@ -157,7 +159,7 @@ namespace Content.Server.Weapon.Melee
{
if (entity.TryGetComponent<IDamageableComponent>(out var damageComponent))
{
damageComponent.ChangeDamage(comp.DamageType, comp.Damage, false, owner);
damageComponent.TryChangeDamage(comp.DamageType, comp.Damage);
}
}
}

View File

@@ -188,7 +188,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
{
if (energyRatio < 1.0)
{
var newDamages = new Dictionary<DamageType, int>(projectileComponent.Damages.Count);
var newDamages = new Dictionary<string, int>(projectileComponent.Damages.Count);
foreach (var (damageType, damage) in projectileComponent.Damages)
{
newDamages.Add(damageType, (int) (damage * energyRatio));

View File

@@ -399,7 +399,7 @@ namespace Content.Server.Weapon.Ranged.Barrels.Components
if (!result.HitEntity.TryGetComponent(out IDamageableComponent? damageable))
return;
damageable.ChangeDamage(hitscan.DamageType, (int)Math.Round(hitscan.Damage, MidpointRounding.AwayFromZero), false, Owner);
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
}

View File

@@ -27,6 +27,8 @@ 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
{
@@ -56,6 +58,17 @@ 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 Func<bool>? WeaponCanFireHandler;
public Func<IEntity, bool>? UserCanFireHandler;
public Action<IEntity, Vector2>? FireHandler;
@@ -168,7 +181,23 @@ namespace Content.Server.Weapon.Ranged
if (ClumsyCheck && ClumsyComponent.TryRollClumsy(user, ClumsyExplodeChance))
{
SoundSystem.Play(
//Wound them
if (user.TryGetComponent(out IDamageableComponent? health))
{
foreach (KeyValuePair<string, int> damage in ClumsyDamage)
{
health.TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(damage.Key), damage.Value);
}
}
// Knock them down
if (user.TryGetComponent(out StunnableComponent? stun))
{
stun.Paralyze(3f);
}
// Apply salt to the wound ("Honk!")
SoundSystem.Play(
Filter.Pvs(Owner), _clumsyWeaponHandlingSound.GetSound(),
Owner.Transform.Coordinates, AudioParams.Default.WithMaxDistance(5));
@@ -176,17 +205,6 @@ namespace Content.Server.Weapon.Ranged
Filter.Pvs(Owner), _clumsyWeaponShotSound.GetSound(),
Owner.Transform.Coordinates, AudioParams.Default.WithMaxDistance(5));
if (user.TryGetComponent(out IDamageableComponent? health))
{
health.ChangeDamage(DamageType.Blunt, 10, false, user);
health.ChangeDamage(DamageType.Heat, 5, false, user);
}
if (user.TryGetComponent(out StunnableComponent? stun))
{
stun.Paralyze(3f);
}
user.PopupMessage(Loc.GetString("server-ranged-weapon-component-try-fire-clumsy"));
Owner.Delete();

View File

@@ -73,6 +73,23 @@ 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();
@@ -81,6 +98,7 @@ 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)
@@ -194,7 +212,7 @@ namespace Content.Shared.Body.Components
{
if (part.IsVital && SlotParts.Count(x => x.Value.PartType == part.PartType) == 0)
{
damageable.ChangeDamage(DamageType.Bloodloss, 300, true); // TODO BODY KILL
damageable.TryChangeDamage(VitalPartsRemovedDamageType, VitalPartsRemovedDamage, true); // TODO BODY KILL
}
}

View File

@@ -18,8 +18,15 @@ namespace Content.Shared.Damage.Components
{
/// <summary>
/// Component that allows attached entities to take damage.
/// This basic version never dies (thus can take an indefinite amount of 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()]
@@ -27,58 +34,76 @@ namespace Content.Shared.Damage.Components
{
public override string Name => "Damageable";
// TODO define these in yaml?
public const string DefaultResistanceSet = "defaultResistances";
public const string DefaultDamageContainer = "metallicDamageContainer";
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly Dictionary<DamageType, int> _damageList = DamageTypeExtensions.ToNewDictionary();
/// <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 = DefaultResistanceSet;
// 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; } = DefaultDamageContainer;
[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
[ViewVariables] public int TotalDamage => _damageList.Values.Sum();
// 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);
[ViewVariables] public IReadOnlyDictionary<DamageClass, int> DamageClasses => _damageList.ToClassDictionary();
// 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);
[ViewVariables] public IReadOnlyDictionary<DamageType, int> DamageTypes => _damageList;
// 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" };
[ViewVariables] public HashSet<DamageType> SupportedTypes { get; } = new();
public HashSet<DamageGroupPrototype> ApplicableDamageGroups { get; } = new();
[ViewVariables] public HashSet<DamageClass> SupportedClasses { get; } = new();
public HashSet<DamageGroupPrototype> FullySupportedDamageGroups { get; } = new();
public bool SupportsDamageClass(DamageClass @class)
{
return SupportedClasses.Contains(@class);
}
public bool SupportsDamageType(DamageType type)
{
return SupportedTypes.Contains(type);
}
public HashSet<DamageTypePrototype> SupportedDamageTypes { get; } = new();
protected override void Initialize()
{
base.Initialize();
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
// TODO DAMAGE Serialize damage done and resistance changes
var damagePrototype = prototypeManager.Index<DamageContainerPrototype>(DamageContainerId);
var damageContainerPrototype = _prototypeManager.Index<DamageContainerPrototype>(DamageContainerId);
SupportedClasses.Clear();
SupportedTypes.Clear();
ApplicableDamageGroups.Clear();
FullySupportedDamageGroups.Clear();
SupportedDamageTypes.Clear();
DamageContainerId = damagePrototype.ID;
SupportedClasses.UnionWith(damagePrototype.SupportedClasses);
SupportedTypes.UnionWith(damagePrototype.SupportedTypes);
//Get Damage groups/types from the DamageContainerPrototype.
DamageContainerId = damageContainerPrototype.ID;
ApplicableDamageGroups.UnionWith(damageContainerPrototype.ApplicableDamageGroups);
FullySupportedDamageGroups.UnionWith(damageContainerPrototype.FullySupportedDamageGroups);
SupportedDamageTypes.UnionWith(damageContainerPrototype.SupportedDamageTypes);
var resistancePrototype = prototypeManager.Index<ResistanceSetPrototype>(ResistanceSetId);
Resistances = new ResistanceSet(resistancePrototype);
//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()
@@ -90,7 +115,7 @@ namespace Content.Shared.Damage.Components
public override ComponentState GetComponentState(ICommonSession player)
{
return new DamageableComponentState(_damageList);
return new DamageableComponentState(GetDamagePerTypeIDs);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
@@ -102,113 +127,101 @@ namespace Content.Shared.Damage.Components
return;
}
_damageList.Clear();
_damageDict.Clear();
foreach (var (type, damage) in state.DamageList)
foreach (var (type, damage) in state.DamageDict)
{
_damageList[type] = damage;
_damageDict[_prototypeManager.Index<DamageTypePrototype>(type)] = damage;
}
}
public int GetDamage(DamageType type)
public int GetDamage(DamageTypePrototype type)
{
return _damageList.GetValueOrDefault(type);
return GetDamagePerType.GetValueOrDefault(type);
}
public bool TryGetDamage(DamageType type, out int damage)
public bool TryGetDamage(DamageTypePrototype type, out int damage)
{
return _damageList.TryGetValue(type, out damage);
return GetDamagePerType.TryGetValue(type, out damage);
}
public int GetDamage(DamageClass @class)
public int GetDamage(DamageGroupPrototype group)
{
if (!SupportsDamageClass(@class))
{
return 0;
}
var damage = 0;
foreach (var type in @class.ToTypes())
{
damage += GetDamage(type);
}
return damage;
return GetDamagePerApplicableGroup.GetValueOrDefault(group);
}
public bool TryGetDamage(DamageClass @class, out int damage)
public bool TryGetDamage(DamageGroupPrototype group, out int damage)
{
if (!SupportsDamageClass(@class))
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))
{
damage = 0;
return false;
}
damage = GetDamage(@class);
if (newValue < 0)
{
// invalid value
return false;
}
foreach (var type in group.DamageTypes)
{
TrySetDamage(type, newValue);
}
return true;
}
/// <summary>
/// Attempts to set the damage value for the given <see cref="DamageType"/>.
/// </summary>
/// <returns>
/// True if successful, false if this container does not support that type.
/// </returns>
public bool TrySetDamage(DamageType type, int newValue)
public bool TrySetAllDamage(int newValue)
{
if (newValue < 0)
{
// invalid value
return false;
}
var damageClass = type.ToClass();
if (SupportedClasses.Contains(damageClass))
foreach (var type in SupportedDamageTypes)
{
var old = _damageList[type] = newValue;
_damageList[type] = newValue;
var delta = newValue - old;
var datum = new DamageChangeData(type, newValue, delta);
var data = new List<DamageChangeData> {datum};
OnHealthChanged(data);
return true;
TrySetDamage(type, newValue);
}
return false;
return true;
}
public void Heal(DamageType type)
public bool TryChangeDamage(DamageTypePrototype type, int amount, bool ignoreDamageResistances = false)
{
SetDamage(type, 0);
}
public void Heal()
{
foreach (var type in SupportedTypes)
{
Heal(type);
}
}
public bool ChangeDamage(
DamageType type,
int amount,
bool ignoreResistances,
IEntity? source = null,
DamageChangeParams? extraParams = null)
{
if (!SupportsDamageType(type))
// 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 (!ignoreResistances)
if (!ignoreDamageResistances)
{
finalDamage = Resistances.CalculateDamage(type, amount);
}
@@ -216,24 +229,23 @@ namespace Content.Shared.Damage.Components
if (finalDamage == 0)
return false;
if (!_damageList.TryGetValue(type, out var current))
{
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;
_damageList[type] = 0;
// Cap healing down to zero
_damageDict[type] = 0;
finalDamage = -current;
}
else
{
_damageList[type] = current + finalDamage;
_damageDict[type] = current + finalDamage;
}
current = _damageList[type];
current = _damageDict[type];
var datum = new DamageChangeData(type, current, finalDamage);
var data = new List<DamageChangeData> {datum};
@@ -243,107 +255,117 @@ namespace Content.Shared.Damage.Components
return true;
}
public bool ChangeDamage(DamageClass @class, int amount, bool ignoreResistances,
IEntity? source = null,
DamageChangeParams? extraParams = null)
public bool TryChangeDamage(DamageGroupPrototype group, int amount, bool ignoreDamageResistances = false)
{
if (!SupportsDamageClass(@class))
{
return false;
}
var types = @class.ToTypes();
var types = group.DamageTypes.ToArray();
if (amount < 0)
{
// Changing multiple types is a bit more complicated. Might be a better way (formula?) to do this,
// but essentially just loops between each damage category until all healing is used up.
var healingLeft = -amount;
var healThisCycle = 1;
// We are Healing. Keep track of how much we can hand out (with a better var name for readability).
var availableHealing = -amount;
// While we have healing left...
while (healingLeft > 0 && healThisCycle != 0)
// 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)
{
// Infinite loop fallback, if no healing was done in a cycle
// then exit
healThisCycle = 0;
int healPerType;
if (healingLeft < types.Count)
{
// Say we were to distribute 2 healing between 3
// this will distribute 1 to each (and stop after 2 are given)
healPerType = 1;
}
else
{
// Say we were to distribute 62 healing between 3
// this will distribute 20 to each, leaving 2 for next loop
healPerType = healingLeft / types.Count;
}
foreach (var type in types)
{
var damage = GetDamage(type);
var healAmount = Math.Min(healingLeft, damage);
healAmount = Math.Min(healAmount, healPerType);
ChangeDamage(type, -healAmount, true);
healThisCycle += healAmount;
healingLeft -= healAmount;
}
}
return true;
}
var damageLeft = amount;
while (damageLeft > 0)
{
int damagePerType;
if (damageLeft < types.Count)
{
damagePerType = 1;
}
else
{
damagePerType = damageLeft / types.Count;
TrySetDamage(group, 0);
return true;
}
// Partially heal each damage group
int healing, damage;
foreach (var type in types)
{
var damageAmount = Math.Min(damagePerType, damageLeft);
ChangeDamage(type, damageAmount, true);
damageLeft -= damageAmount;
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;
}
return true;
// amount==0 no damage change.
return false;
}
public bool SetDamage(DamageType type, int newValue, IEntity? source = null, DamageChangeParams? extraParams = null)
public bool TrySetDamage(DamageTypePrototype type, int newValue)
{
if (newValue >= TotalDamage)
if (!_damageDict.TryGetValue(type, out var oldValue))
{
return false;
}
if (newValue < 0)
{
// invalid value
return false;
}
if (!_damageList.ContainsKey(type))
if (oldValue == newValue)
{
return false;
// No health change.
// But we are trying to set, not trying to change.
return true;
}
var old = _damageList[type];
_damageList[type] = newValue;
_damageDict[type] = newValue;
var delta = newValue - old;
var delta = newValue - oldValue;
var datum = new DamageChangeData(type, 0, delta);
var data = new List<DamageChangeData> {datum};
@@ -356,7 +378,7 @@ namespace Content.Shared.Damage.Components
{
var data = new List<DamageChangeData>();
foreach (var type in SupportedTypes)
foreach (var type in SupportedDamageTypes)
{
var damage = GetDamage(type);
var datum = new DamageChangeData(type, damage, 0);
@@ -382,11 +404,15 @@ namespace Content.Shared.Damage.Components
Dirty();
}
void IRadiationAct.RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
public void RadiationAct(float frameTime, SharedRadiationPulseComponent radiation)
{
var totalDamage = Math.Max((int)(frameTime * radiation.RadsPerSecond), 1);
ChangeDamage(DamageType.Radiation, totalDamage, false, radiation.Owner);
foreach (var typeID in RadiationDamageTypeIDs)
{
TryChangeDamage(_prototypeManager.Index<DamageTypePrototype>(typeID), totalDamage);
}
}
public void OnExplosion(ExplosionEventArgs eventArgs)
@@ -399,19 +425,75 @@ namespace Content.Shared.Damage.Components
_ => throw new ArgumentOutOfRangeException()
};
ChangeDamage(DamageType.Piercing, damage, false);
ChangeDamage(DamageType.Heat, damage, false);
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 Dictionary<DamageType, int> DamageList;
public readonly IReadOnlyDictionary<string, int> DamageDict;
public DamageableComponentState(IReadOnlyDictionary<string, int> damageDict)
public DamageableComponentState(Dictionary<DamageType, int> damageList)
{
DamageList = damageList;
DamageDict = damageDict;
}
}
}

View File

@@ -8,137 +8,214 @@ namespace Content.Shared.Damage.Components
public interface IDamageableComponent : IComponent, IExAct
{
/// <summary>
/// Sum of all damages taken.
/// The sum of all damages types in the DamageableComponent.
/// </summary>
int TotalDamage { get; }
/// <summary>
/// The amount of damage mapped by <see cref="DamageClass"/>.
/// Returns a dictionary of the damage in the container, indexed by applicable <see cref="DamageGroupPrototype"/>.
/// </summary>
IReadOnlyDictionary<DamageClass, int> DamageClasses { get; }
/// <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>
/// The amount of damage mapped by <see cref="DamageType"/>.
/// Returns a dictionary of the damage in the container, indexed by fully supported instances of <see
/// cref="DamageGroupPrototype"/>.
/// </summary>
IReadOnlyDictionary<DamageType, int> DamageTypes { get; }
/// <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; }
HashSet<DamageType> SupportedTypes { get; }
/// <summary>
/// Returns a dictionary of the damage in the container, indexed by <see cref="DamageTypePrototype"/>.
/// </summary>
IReadOnlyDictionary<DamageTypePrototype, int> GetDamagePerType { get; }
HashSet<DamageClass> SupportedClasses { 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; }
bool SupportsDamageClass(DamageClass @class);
bool SupportsDamageType(DamageType type);
/// <summary>
/// Gets the amount of damage of a type.
/// 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(DamageType type, out int damage);
bool TryGetDamage(DamageTypePrototype type, out int damage);
/// <summary>
/// Gets the amount of damage of a class.
/// Returns the amount of damage of a given type, or zero if it is not supported.
/// </summary>
/// <param name="class">The class to get the damage of.</param>
/// <param name="damage">The amount of damage of that class.</param>
/// <returns>
/// True if the given <see cref="@class"/> is supported, false otherwise.
/// </returns>
bool TryGetDamage(DamageClass @class, out int damage);
int GetDamage(DamageTypePrototype type);
/// <summary>
/// Changes the specified <see cref="DamageType"/>, applying
/// resistance values only if it is damage.
/// 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="ignoreResistances">
/// Whether or not to ignore resistances.
/// <param name="ignoreDamageResistances">
/// Whether or not to ignore resistances when taking damage.
/// Healing always ignores resistances, regardless of this input.
/// </param>
/// <param name="source">
/// The entity that dealt or healed the damage, if any.
/// </param>
/// <param name="extraParams">
/// Extra parameters that some components may require, such as a specific limb to target.
/// </param>
/// <returns>
/// False if the given type is not supported or improper
/// <see cref="DamageChangeParams"/> were provided; true otherwise.
/// False if the given type is not supported or no damage change occurred; true otherwise.
/// </returns>
bool ChangeDamage(
DamageType type,
int amount,
bool ignoreResistances,
IEntity? source = null,
DamageChangeParams? extraParams = null);
bool TryChangeDamage(DamageTypePrototype type, int amount, bool ignoreDamageResistances = false);
/// <summary>
/// Changes the specified <see cref="DamageClass"/>, applying
/// resistance values only if it is damage.
/// Spreads amount evenly between the <see cref="DamageType"></see>s
/// represented by that class.
/// Tries to change damage of the specified <see cref="DamageGroupPrototype"/>, applying resistance values
/// only if it is damage.
/// </summary>
/// <param name="class">Class of damage being changed.</param>
/// <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="ignoreResistances">
/// Whether to ignore resistances.
/// Healing always ignores resistances, regardless of this input.
/// </param>
/// <param name="source">Entity that dealt or healed the damage, if any.</param>
/// <param name="extraParams">
/// Extra parameters that some components may require,
/// such as a specific limb to target.
/// <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 class is not supported or improper
/// <see cref="DamageChangeParams"/> were provided; true otherwise.
/// Returns false if the given group is not applicable or no damage change occurred; true otherwise.
/// </returns>
bool ChangeDamage(
DamageClass @class,
int amount,
bool ignoreResistances,
IEntity? source = null,
DamageChangeParams? extraParams = null);
bool TryChangeDamage(DamageGroupPrototype group, int amount, bool ignoreDamageResistances = false);
/// <summary>
/// Forcefully sets the specified <see cref="DamageType"/> to the given
/// value, ignoring resistance values.
/// Forcefully sets the specified <see cref="DamageTypePrototype"/> to the given value, ignoring resistance
/// values.
/// </summary>
/// <param name="type">Type of damage being changed.</param>
/// <param name="type">Type of damage being set.</param>
/// <param name="newValue">New damage value to be set.</param>
/// <param name="source">Entity that set the new damage value.</param>
/// <param name="extraParams">
/// Extra parameters that some components may require,
/// such as a specific limb to target.
/// </param>
/// <returns>
/// Returns false if the given type is not supported or improper
/// <see cref="DamageChangeParams"/> were provided; true otherwise.
/// Returns false if a given type is not supported or a negative value is provided; true otherwise.
/// </returns>
bool SetDamage(
DamageType type,
int newValue,
IEntity? source = null,
DamageChangeParams? extraParams = null);
bool TrySetDamage(DamageTypePrototype type, int newValue);
/// <summary>
/// Sets all damage values to zero.
/// Forcefully sets all damage types in a specified damage group using <see cref="TrySetDamage"></see>.
/// </summary>
void Heal();
/// <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.

View File

@@ -1,5 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -8,45 +9,142 @@ using Robust.Shared.ViewVariables;
namespace Content.Shared.Damage.Container
{
/// <summary>
/// Prototype for the DamageContainer class.
/// 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
{
[DataField("supportAll")] private bool _supportAll;
[DataField("supportedClasses")] private HashSet<DamageClass> _supportedClasses = new();
[DataField("supportedTypes")] private HashSet<DamageType> _supportedTypes = new();
// TODO NET 5 IReadOnlySet
[ViewVariables] public IReadOnlyCollection<DamageClass> SupportedClasses => _supportedClasses;
[ViewVariables] public IReadOnlyCollection<DamageType> SupportedTypes => _supportedTypes;
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)
{
_supportedClasses.UnionWith(Enum.GetValues<DamageClass>());
_supportedTypes.UnionWith(Enum.GetValues<DamageType>());
foreach (var group in _prototypeManager.EnumeratePrototypes<DamageGroupPrototype>())
{
_applicableDamageGroups.Add(group);
_fullySupportedDamageGroups.Add(group);
}
foreach (var type in _prototypeManager.EnumeratePrototypes<DamageTypePrototype>())
{
_supportedDamageTypes.Add(type);
}
return;
}
foreach (var supportedClass in _supportedClasses)
// Add fully supported damage groups
foreach (var groupID in _supportedDamageGroupIDs)
{
foreach (var supportedType in supportedClass.ToTypes())
var group = _prototypeManager.Index<DamageGroupPrototype>(groupID);
_fullySupportedDamageGroups.Add(group);
foreach (var type in group.DamageTypes)
{
_supportedTypes.Add(supportedType);
_supportedDamageTypes.Add(type);
}
}
foreach (var originalType in _supportedTypes)
// 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)
{
_supportedClasses.Add(originalType.ToClass());
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);
}
}
}
}
}

View File

@@ -2,14 +2,14 @@
{
/// <summary>
/// Data class with information on how the value of a
/// single <see cref="DamageType"/> has changed.
/// single <see cref="DamageTypePrototype"/> has changed.
/// </summary>
public struct DamageChangeData
{
/// <summary>
/// Type of damage that changed.
/// </summary>
public DamageType Type;
public DamageTypePrototype Type;
/// <summary>
/// The new current value for that damage.
@@ -21,7 +21,7 @@
/// </summary>
public int Delta;
public DamageChangeData(DamageType type, int newValue, int delta)
public DamageChangeData(DamageTypePrototype type, int newValue, int delta)
{
Type = type;
NewValue = newValue;

View File

@@ -1,18 +0,0 @@
using System;
using Content.Shared.Body.Components;
using Content.Shared.Damage.Components;
namespace Content.Shared.Damage
{
/// <summary>
/// Data class with information on how to damage a
/// <see cref="IDamageableComponent"/>.
/// While not necessary to damage for all instances, classes such as
/// <see cref="SharedBodyComponent"/> may require it for extra data
/// (such as selecting which limb to target).
/// </summary>
// TODO BODY: Remove and pretend it never existed
public class DamageChangeParams : EventArgs
{
}
}

View File

@@ -12,7 +12,7 @@ namespace Content.Shared.Damage
Data = data;
}
public DamageChangedEventArgs(IDamageableComponent damageable, DamageType type, int newValue, int delta)
public DamageChangedEventArgs(IDamageableComponent damageable, DamageTypePrototype type, int newValue, int delta)
{
Damageable = damageable;
@@ -28,7 +28,7 @@ namespace Content.Shared.Damage
public IDamageableComponent Damageable { get; }
/// <summary>
/// List containing data on each <see cref="DamageType"/> that was changed.
/// List containing data on each <see cref="DamageTypePrototype"/> that was changed.
/// </summary>
public IReadOnlyList<DamageChangeData> Data { get; }
}

View File

@@ -12,7 +12,7 @@ namespace Content.Shared.Damage
Data = data;
}
public DamageChangedMessage(IDamageableComponent damageable, DamageType type, int newValue, int delta)
public DamageChangedMessage(IDamageableComponent damageable, DamageTypePrototype type, int newValue, int delta)
{
Damageable = damageable;
@@ -28,7 +28,7 @@ namespace Content.Shared.Damage
public IDamageableComponent Damageable { get; }
/// <summary>
/// List containing data on each <see cref="DamageType"/> that was changed.
/// List containing data on each <see cref="DamageTypePrototype"/> that was changed.
/// </summary>
public IReadOnlyList<DamageChangeData> Data { get; }

View File

@@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Robust.Shared.Serialization;
namespace Content.Shared.Damage
{
[Serializable, NetSerializable]
public enum DamageClass
{
Brute,
Burn,
Toxin,
Airloss,
Genetic
}
public static class DamageClassExtensions
{
public static ImmutableList<DamageType> ToTypes(this DamageClass @class)
{
return DamageSystem.ClassToType[@class];
}
public static Dictionary<DamageClass, T> ToNewDictionary<T>() where T : struct
{
return Enum.GetValues(typeof(DamageClass))
.Cast<DamageClass>()
.ToDictionary(@class => @class, _ => default(T));
}
public static Dictionary<DamageClass, int> ToNewDictionary()
{
return ToNewDictionary<int>();
}
}
}

View File

@@ -0,0 +1,42 @@
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));
}
}
}
}

View File

@@ -8,59 +8,6 @@ namespace Content.Shared.Damage
[UsedImplicitly]
public class DamageSystem : EntitySystem
{
public static ImmutableDictionary<DamageClass, ImmutableList<DamageType>> ClassToType { get; } = DefaultClassToType();
public static ImmutableDictionary<DamageType, DamageClass> TypeToClass { get; } = DefaultTypeToClass();
private static ImmutableDictionary<DamageClass, ImmutableList<DamageType>> DefaultClassToType()
{
return new Dictionary<DamageClass, ImmutableList<DamageType>>
{
[DamageClass.Brute] = new List<DamageType>
{
DamageType.Blunt,
DamageType.Slash,
DamageType.Piercing
}.ToImmutableList(),
[DamageClass.Burn] = new List<DamageType>
{
DamageType.Heat,
DamageType.Shock,
DamageType.Cold
}.ToImmutableList(),
[DamageClass.Toxin] = new List<DamageType>
{
DamageType.Poison,
DamageType.Radiation
}.ToImmutableList(),
[DamageClass.Airloss] = new List<DamageType>
{
DamageType.Asphyxiation,
DamageType.Bloodloss
}.ToImmutableList(),
[DamageClass.Genetic] = new List<DamageType>
{
DamageType.Cellular
}.ToImmutableList()
}.ToImmutableDictionary();
}
private static ImmutableDictionary<DamageType, DamageClass> DefaultTypeToClass()
{
return new Dictionary<DamageType, DamageClass>
{
{DamageType.Blunt, DamageClass.Brute},
{DamageType.Slash, DamageClass.Brute},
{DamageType.Piercing, DamageClass.Brute},
{DamageType.Heat, DamageClass.Burn},
{DamageType.Shock, DamageClass.Burn},
{DamageType.Cold, DamageClass.Burn},
{DamageType.Poison, DamageClass.Toxin},
{DamageType.Radiation, DamageClass.Toxin},
{DamageType.Asphyxiation, DamageClass.Airloss},
{DamageType.Bloodloss, DamageClass.Airloss},
{DamageType.Cellular, DamageClass.Genetic}
}.ToImmutableDictionary();
}
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Serialization;
namespace Content.Shared.Damage
{
[Serializable, NetSerializable]
public enum DamageType
{
Blunt,
Slash,
Piercing,
Heat,
Shock,
Cold,
Poison,
Radiation,
Asphyxiation,
Bloodloss,
Cellular
}
public static class DamageTypeExtensions
{
public static DamageClass ToClass(this DamageType type)
{
return DamageSystem.TypeToClass[type];
}
public static Dictionary<DamageType, T> ToNewDictionary<T>() where T : struct
{
return Enum.GetValues(typeof(DamageType))
.Cast<DamageType>()
.ToDictionary(type => type, _ => default(T));
}
public static Dictionary<DamageType, int> ToNewDictionary()
{
return ToNewDictionary<int>();
}
public static Dictionary<DamageClass, int> ToClassDictionary(this IReadOnlyDictionary<DamageType, int> types)
{
var classes = DamageClassExtensions.ToNewDictionary();
foreach (var @class in classes.Keys.ToList())
{
foreach (var type in @class.ToTypes())
{
if (!types.TryGetValue(type, out var damage))
{
continue;
}
classes[@class] += damage;
}
}
return classes;
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Damage
{
/// <summary>
/// A single damage type. These types are grouped together in <see cref="DamageGroupPrototype"/>s.
/// </summary>
[Prototype("damageType")]
[Serializable, NetSerializable]
public class DamageTypePrototype : IPrototype
{
[DataField("id", required: true)]
public string ID { get; } = default!;
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -7,18 +9,21 @@ namespace Content.Shared.Damage.Resistances
{
/// <summary>
/// Set of resistances used by damageable objects.
/// Each <see cref="DamageType"/> has a multiplier and flat damage
/// 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()
{
foreach (var damageType in (DamageType[]) Enum.GetValues(typeof(DamageType)))
{
Resistances.Add(damageType, new ResistanceSetSettings(1f, 0));
}
}
public ResistanceSet(ResistanceSetPrototype data)
@@ -27,12 +32,6 @@ namespace Content.Shared.Damage.Resistances
Resistances = data.Resistances;
}
[ViewVariables]
public string ID { get; } = string.Empty;
[ViewVariables]
public Dictionary<DamageType, ResistanceSetSettings> Resistances { get; } = new();
/// <summary>
/// Adjusts input damage with the resistance set values.
/// Only applies reduction if the amount is damage (positive), not
@@ -40,11 +39,18 @@ namespace Content.Shared.Damage.Resistances
/// </summary>
/// <param name="damageType">Type of damage.</param>
/// <param name="amount">Incoming amount of damage.</param>
public int CalculateDamage(DamageType damageType, int amount)
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 -= Resistances[damageType].FlatReduction;
amount -= resistance.FlatReduction;
if (amount <= 0)
{
@@ -52,25 +58,9 @@ namespace Content.Shared.Damage.Resistances
}
}
amount = (int) Math.Ceiling(amount * Resistances[damageType].Coefficient);
amount = (int) Math.Ceiling(amount * resistance.Coefficient);
return amount;
}
}
/// <summary>
/// Settings for a specific damage type in a resistance set. Flat reduction is 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;
}
}
}

View File

@@ -1,5 +1,7 @@
using System;
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;
@@ -14,29 +16,46 @@ namespace Content.Shared.Damage.Resistances
[Serializable, NetSerializable]
public class ResistanceSetPrototype : IPrototype, ISerializationHooks
{
[ViewVariables]
[DataField("coefficients")]
public Dictionary<DamageType, float> Coefficients { get; } = new();
[ViewVariables]
[DataField("flatReductions")]
public Dictionary<DamageType, int> FlatReductions { get; } = new();
[ViewVariables]
public Dictionary<DamageType, ResistanceSetSettings> Resistances { get; private set; } = new();
[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()
{
Resistances = new Dictionary<DamageType, ResistanceSetSettings>();
foreach (var damageType in (DamageType[]) Enum.GetValues(typeof(DamageType)))
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var damageTypeID in coefficients.Keys)
{
Resistances.Add(damageType,
new ResistanceSetSettings(Coefficients[damageType], FlatReductions[damageType]));
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;
}
}
}

View File

@@ -16,25 +16,25 @@ namespace Content.Shared.MedicalScanner
public class MedicalScannerBoundUserInterfaceState : BoundUserInterfaceState
{
public readonly EntityUid? Entity;
public readonly Dictionary<DamageClass, int> DamageClasses;
public readonly Dictionary<DamageType, int> DamageTypes;
public readonly Dictionary<string, int> DamagePerSupportedGroupID;
public readonly Dictionary<string, int> DamagePerTypeID;
public readonly bool IsScanned;
public MedicalScannerBoundUserInterfaceState(
EntityUid? entity,
Dictionary<DamageClass, int> damageClasses,
Dictionary<DamageType, int> damageTypes,
Dictionary<string, int> damagePerSupportedGroupID,
Dictionary<string, int> damagePerTypeID,
bool isScanned)
{
Entity = entity;
DamageClasses = damageClasses;
DamageTypes = damageTypes;
DamagePerSupportedGroupID = damagePerSupportedGroupID;
DamagePerTypeID = damagePerTypeID;
IsScanned = isScanned;
}
public bool HasDamage()
{
return DamageClasses.Count > 0 || DamageTypes.Count > 0;
return DamagePerSupportedGroupID.Count > 0 || DamagePerTypeID.Count > 0;
}
}

View File

@@ -16,5 +16,7 @@ medical-scanner-eject-verb-get-data-text = Eject
medical-scanner-window-save-button-text = Scan and Save DNA
medical-scanner-window-no-patient-data-text = No patient data.
medical-scanner-window-entity-health-text = {$entityName}'s health:
medical-scanner-window-damage-class-text = {$damageClass}: {$amount}
medical-scanner-window-entity-damage-total-text = Total Damage: {$amount}
medical-scanner-window-damage-group-text = {$damageGroup}: {$amount}
medical-scanner-window-damage-type-text = {$damageType}: {$amount}
medical-scanner-window-damage-type-duplicate-text = {$damageType}: {$amount} (duplicate)

View File

@@ -124,36 +124,36 @@
Arithrazine:
effects:
- !type:HealthChange
damageClass: Toxin
damageGroup: Toxin
healthChange: -1
- !type:HealthChange
damageClass: Brute
damageGroup: Brute
healthChange: 0.5
Bicaridine:
effects:
- !type:HealthChange
damageClass: Brute
damageGroup: Brute
healthChange: -2
Dermaline:
effects:
- !type:HealthChange
damageClass: Burn
damageGroup: Burn
healthChange: -3
Dexalin:
effects:
- !type:HealthChange
damageClass: Airloss
damageGroup: Airloss
healthChange: -1
DexalinPlus:
effects:
- !type:HealthChange
damageClass: Airloss
damageGroup: Airloss
healthChange: -3
Dylovene:
effects:
- !type:HealthChange
damageClass: Toxin
healthChange: -1
- !type:HealthChange
damageGroup: Toxin
healthChange: -1
Ephedrine:
effects:
- !type:MovespeedModifier
@@ -162,23 +162,23 @@
HeartbreakerToxin:
effects:
- !type:HealthChange
damageClass: Airloss
damageGroup: Airloss
healthChange: 4
Kelotane:
effects:
- !type:HealthChange
damageClass: Burn
damageGroup: Burn
healthChange: -1
Lexorin:
effects:
- !type:HealthChange
damageClass: Airloss
damageGroup: Airloss
healthChange: 7
Meth:
effects:
- !type:HealthChange
healthChange: 2.5
damageClass: Toxin
damageGroup: Toxin
- !type:MovespeedModifier
walkSpeedModifier: 1.3
sprintSpeedModifier: 1.3
@@ -186,20 +186,20 @@
effects:
- !type:HealthChange
healthChange: -2
damageClass: Burn
damageGroup: Burn
- !type:HealthChange
healthChange: -2
damageClass: Toxin
damageGroup: Toxin
- !type:HealthChange
healthChange: -2
damageClass: Airloss
damageGroup: Airloss
- !type:HealthChange
healthChange: -2
damageClass: Brute
damageGroup: Brute
Synaptizine:
effects:
- !type:HealthChange
damageClass: Toxin
damageGroup: Toxin
healthChange: 0.5
- type: entity
@@ -259,7 +259,7 @@
effects:
- !type:SatiateThirst
- !type:HealthChange
damageClass: Toxin
damageGroup: Toxin
healthChange: 1
JuiceWatermelon:
effects:
@@ -304,7 +304,7 @@
- !type:SatiateThirst
hydrationFactor: 2
- !type:HealthChange
damageClass: Toxin
damageGroup: Toxin
healthChange: 1
- type: entity

View File

@@ -0,0 +1,97 @@
# Silver: Todo break out into damage_type,damage_class, damage_container yml files when we support loading prototypes by priority.
- 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: damageContainer
id: allDamageContainer
supportAll: true
- type: damageContainer
id: biologicalDamageContainer
supportedGroups:
- Brute
- Burn
- Toxin
- Airloss
- Genetic
- type: damageContainer
id: metallicDamageContainer
supportedGroups:
- Brute
- Burn

View File

@@ -1,18 +0,0 @@
- type: damageContainer
id: allDamageContainer
supportAll: true
- type: damageContainer
id: biologicalDamageContainer
supportedClasses:
- Brute
- Burn
- Toxin
- Airloss
- Genetic
- type: damageContainer
id: metallicDamageContainer
supportedClasses:
- Brute
- Burn

View File

@@ -34,6 +34,7 @@
- type: MovementSpeedModifier
- type: MovedByPressure
- type: Barotrauma
damageType: Blunt
- type: DamageOnHighSpeedImpact
soundHit:
path: /Audio/Effects/hit_kick.ogg
@@ -157,7 +158,9 @@
fireSpread: true
canResistFire: true
- type: Temperature
heatDamageType: Heat
heatDamageThreshold: 360
coldDamageType: Cold
coldDamageThreshold: 260
currentTemperature: 310.15
specificHeat: 42
@@ -191,7 +194,7 @@
thresholds:
- trigger:
!type:DamageTypeTrigger
type: Blunt
damageType: Blunt
damage: 400
behaviors:
- !type:GibBehavior { }

View File

@@ -1,4 +1,4 @@
# SKREEEEEEEEEEE
# Vox bad. moff best.
- type: entity
parent: HumanMob_Content
abstract: True