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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
||||
{
|
||||
comp.OnUpdate(_accumulatedFrameTime);
|
||||
}
|
||||
_accumulatedFrameTime -= 1;
|
||||
_accumulatedFrameTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Content.Shared/Damage/DamageGroupPrototype.cs
Normal file
42
Content.Shared/Damage/DamageGroupPrototype.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Content.Shared/Damage/DamageTypePrototype.cs
Normal file
18
Content.Shared/Damage/DamageTypePrototype.cs
Normal 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!;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
97
Resources/Prototypes/Damage/damage.yml
Normal file
97
Resources/Prototypes/Damage/damage.yml
Normal 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
|
||||
@@ -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
|
||||
@@ -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 { }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SKREEEEEEEEEEE
|
||||
# Vox bad. moff best.
|
||||
- type: entity
|
||||
parent: HumanMob_Content
|
||||
abstract: True
|
||||
|
||||
Reference in New Issue
Block a user