Damage rework (#2525)
* Make damage work through messages and events, make destructible not inherit ruinable or reference damageable * Copy sound logic to destructible component for now * Fix typo * Fix prototype error * Remove breakable component damageable reference * Remove breakable construction reference * Remove ruinable component * Move thresholds to individual components and away from damageable * Add threshold property to damageable component code * Add thresholds to destructible component, add states to damageable, remove damage container, fix up mob states * Being alive isn't normal * Fix not reading the id * Merge fixes * YAML fixes * Grammar moment * Remove unnecessary dependency * Update thresholds doc * Change naming of thresholds to states in MobStateComponent * Being alive is once again normal * Make DamageState a byte * Bring out classes structs and enums from DestructibleComponent * Add test for destructible thresholds * Merge fixes * More merge fixes and fix rejuvenate test * Remove IMobState.IsConscious * More merge fixes someone please god review this shit already * Fix rejuvenate test * Update outdated destructible in YAML * Fix repeatedly entering the current state * Fix repeatedly entering the current state, add Threshold.TriggersOnce and expand test * Update saltern
This commit is contained in:
@@ -144,11 +144,10 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
|
||||
{
|
||||
BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Owner.Name)}";
|
||||
|
||||
// TODO BODY Make dead not be the destroy threshold for a body part
|
||||
if (part.Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||
damageable.TryHealth(DamageState.Critical, out var health))
|
||||
// TODO BODY Part damage
|
||||
if (part.Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
BodyPartHealth.Text = $"{health.current} / {health.max}";
|
||||
BodyPartHealth.Text = Loc.GetString("{0} damage", damageable.TotalDamage);
|
||||
}
|
||||
|
||||
MechanismList.Clear();
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace Content.Client.GameObjects.Components.Kitchen
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private GrinderMenu _menu;
|
||||
private Dictionary<int, EntityUid> _chamberVisualContents = new Dictionary<int, EntityUid>();
|
||||
private Dictionary<int, Solution.ReagentQuantity> _beakerVisualContents = new Dictionary<int, Solution.ReagentQuantity>();
|
||||
private Dictionary<int, EntityUid> _chamberVisualContents = new();
|
||||
private Dictionary<int, Solution.ReagentQuantity> _beakerVisualContents = new();
|
||||
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||
|
||||
protected override void Open()
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class CriticalMobState : SharedCriticalMobState
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class CriticalState : SharedCriticalState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
|
||||
}
|
||||
|
||||
EntitySystem.Get<StandingStateSystem>().Down(entity);
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -9,10 +8,12 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class DeadState : SharedDeadState
|
||||
public class DeadMobState : SharedDeadMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
|
||||
@@ -28,6 +29,8 @@ namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
base.ExitState(entity);
|
||||
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out PhysicsComponent physics))
|
||||
@@ -35,7 +38,5 @@ namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
physics.CanCollide = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMobStateComponent))]
|
||||
[ComponentReference(typeof(IMobStateComponent))]
|
||||
public class MobStateComponent : SharedMobStateComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMobStateManagerComponent))]
|
||||
public class MobStateManagerComponent : SharedMobStateManagerComponent
|
||||
{
|
||||
private readonly Dictionary<DamageState, IMobState> _behavior = new()
|
||||
{
|
||||
{DamageState.Alive, new NormalState()},
|
||||
{DamageState.Critical, new CriticalState()},
|
||||
{DamageState.Dead, new DeadState()}
|
||||
};
|
||||
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
protected override IReadOnlyDictionary<DamageState, IMobState> Behavior => _behavior;
|
||||
|
||||
public override DamageState CurrentDamageState
|
||||
{
|
||||
get => _currentDamageState;
|
||||
protected set
|
||||
{
|
||||
if (_currentDamageState == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentDamageState != DamageState.Invalid)
|
||||
{
|
||||
CurrentMobState.ExitState(Owner);
|
||||
}
|
||||
|
||||
_currentDamageState = value;
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not MobStateManagerComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentDamageState = state.DamageState;
|
||||
CurrentMobState?.ExitState(Owner);
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,20 @@
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class NormalState : SharedNormalState
|
||||
public class NormalMobState : SharedNormalMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
|
||||
}
|
||||
|
||||
UpdateState(entity);
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity) { }
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Server.GlobalVerbs;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
@@ -14,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
[TestOf(typeof(RejuvenateVerb))]
|
||||
public class RejuvenateTest : ContentIntegrationTest
|
||||
{
|
||||
private const string PROTOTYPES = @"
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
name: DamageableDummy
|
||||
id: DamageableDummy
|
||||
@@ -23,12 +24,17 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
damagePrototype: biologicalDamageContainer
|
||||
criticalThreshold: 100
|
||||
deadThreshold: 200
|
||||
- type: MobState
|
||||
thresholds:
|
||||
0: !type:NormalMobState {}
|
||||
100: !type:CriticalMobState {}
|
||||
200: !type:DeadMobState {}
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task RejuvenateDeadTest()
|
||||
{
|
||||
var options = new ServerIntegrationOptions{ExtraPrototypes = PROTOTYPES};
|
||||
var options = new ServerIntegrationOptions{ExtraPrototypes = Prototypes};
|
||||
var server = StartServerDummyTicker(options);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
@@ -43,19 +49,30 @@ namespace Content.IntegrationTests.Tests.Commands
|
||||
|
||||
// Sanity check
|
||||
Assert.True(human.TryGetComponent(out IDamageableComponent damageable));
|
||||
Assert.That(damageable.CurrentState, Is.EqualTo(DamageState.Alive));
|
||||
Assert.True(human.TryGetComponent(out IMobStateComponent mobState));
|
||||
Assert.That(mobState.IsAlive, Is.True);
|
||||
Assert.That(mobState.IsCritical, Is.False);
|
||||
Assert.That(mobState.IsDead, Is.False);
|
||||
Assert.That(mobState.IsIncapacitated, Is.False);
|
||||
|
||||
// Kill the entity
|
||||
damageable.ChangeDamage(DamageClass.Brute, 10000000, true);
|
||||
|
||||
// Check that it is dead
|
||||
Assert.That(damageable.CurrentState, Is.EqualTo(DamageState.Dead));
|
||||
Assert.That(mobState.IsAlive, Is.False);
|
||||
Assert.That(mobState.IsCritical, Is.False);
|
||||
Assert.That(mobState.IsDead, Is.True);
|
||||
Assert.That(mobState.IsIncapacitated, Is.True);
|
||||
|
||||
// Rejuvenate them
|
||||
RejuvenateVerb.PerformRejuvenate(human);
|
||||
|
||||
// Check that it is alive and with no damage
|
||||
Assert.That(damageable.CurrentState, Is.EqualTo(DamageState.Alive));
|
||||
Assert.That(mobState.IsAlive, Is.True);
|
||||
Assert.That(mobState.IsCritical, Is.False);
|
||||
Assert.That(mobState.IsDead, Is.False);
|
||||
Assert.That(mobState.IsIncapacitated, Is.False);
|
||||
|
||||
Assert.That(damageable.TotalDamage, Is.Zero);
|
||||
});
|
||||
}
|
||||
|
||||
294
Content.IntegrationTests/Tests/Destructible/DestructibleTests.cs
Normal file
294
Content.IntegrationTests/Tests/Destructible/DestructibleTests.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Destructible;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Destructible
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DestructibleComponent))]
|
||||
[TestOf(typeof(Threshold))]
|
||||
public class DestructibleTests : ContentIntegrationTest
|
||||
{
|
||||
private static readonly string DestructibleEntityId = "DestructibleTestsDestructibleEntity";
|
||||
|
||||
private static readonly string Prototypes = $@"
|
||||
- type: entity
|
||||
id: {DestructibleEntityId}
|
||||
name: {DestructibleEntityId}
|
||||
components:
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
20:
|
||||
TriggersOnce: false
|
||||
50:
|
||||
Sound: /Audio/Effects/woodhit.ogg
|
||||
Spawn:
|
||||
WoodPlank:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: [""Breakage""]
|
||||
TriggersOnce: false
|
||||
- type: TestThresholdListener
|
||||
";
|
||||
|
||||
private class TestThresholdListenerComponent : Component
|
||||
{
|
||||
public override string Name => "TestThresholdListener";
|
||||
|
||||
public List<DestructibleThresholdReachedMessage> ThresholdsReached { get; } = new();
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DestructibleThresholdReachedMessage msg:
|
||||
ThresholdsReached.Add(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestThresholdActivation()
|
||||
{
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption
|
||||
{
|
||||
ExtraPrototypes = Prototypes,
|
||||
ContentBeforeIoC = () =>
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().Register<TestThresholdListenerComponent>();
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
IEntity sDestructibleEntity = null;
|
||||
IDamageableComponent sDamageableComponent = null;
|
||||
DestructibleComponent sDestructibleComponent = null;
|
||||
TestThresholdListenerComponent sThresholdListenerComponent = null;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapId = new MapId(1);
|
||||
var coordinates = new MapCoordinates(0, 0, mapId);
|
||||
sMapManager.CreateMap(mapId);
|
||||
|
||||
sDestructibleEntity = sEntityManager.SpawnEntity(DestructibleEntityId, coordinates);
|
||||
sDamageableComponent = sDestructibleEntity.GetComponent<IDamageableComponent>();
|
||||
sDestructibleComponent = sDestructibleEntity.GetComponent<DestructibleComponent>();
|
||||
sThresholdListenerComponent = sDestructibleEntity.GetComponent<TestThresholdListenerComponent>();
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.Zero);
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
|
||||
|
||||
// No thresholds reached yet, the earliest one is at 20 damage
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.Zero);
|
||||
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true));
|
||||
|
||||
// Only one threshold reached, 20
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
var msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
|
||||
// Check that it matches the total damage dealt
|
||||
Assert.That(msg.TotalDamage, Is.EqualTo(20));
|
||||
|
||||
var threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Acts, Is.EqualTo(0));
|
||||
Assert.That(threshold.Sound, Is.Null.Or.Empty);
|
||||
Assert.That(threshold.Spawn, Is.Null);
|
||||
Assert.That(threshold.SoundCollection, Is.Null.Or.Empty);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 30, true));
|
||||
|
||||
// Only one threshold reached, 50, since 20 was already reached before
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
|
||||
// Check that it matches the total damage dealt
|
||||
Assert.That(msg.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Acts, Is.EqualTo((int) ThresholdActs.Breakage));
|
||||
Assert.That(threshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
|
||||
Assert.That(threshold.Spawn, Is.Not.Null);
|
||||
Assert.That(threshold.Spawn.Count, Is.EqualTo(1));
|
||||
Assert.That(threshold.Spawn.Single().Key, Is.EqualTo("WoodPlank"));
|
||||
Assert.That(threshold.Spawn.Single().Value.Min, Is.EqualTo(1));
|
||||
Assert.That(threshold.Spawn.Single().Value.Max, Is.EqualTo(1));
|
||||
Assert.That(threshold.SoundCollection, Is.Null.Or.Empty);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Damage for 50 again, up to 100 now
|
||||
Assert.True(sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true));
|
||||
|
||||
// No new thresholds reached as even though they don't only trigger once, the entity was not healed below the threshold
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Heal the entity for 40 damage, down to 60
|
||||
sDamageableComponent.ChangeDamage(DamageType.Blunt, -40, true);
|
||||
|
||||
// Thresholds don't work backwards
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Damage for 10, up to 70
|
||||
sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true);
|
||||
|
||||
// Not enough healing to de-trigger a threshold
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Heal by 30, down to 40
|
||||
sDamageableComponent.ChangeDamage(DamageType.Blunt, -30, true);
|
||||
|
||||
// Thresholds don't work backwards
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Damage up to 50 again
|
||||
sDamageableComponent.ChangeDamage(DamageType.Blunt, 10, true);
|
||||
|
||||
// The 50 threshold should have triggered again, after being healed
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached.Count, Is.EqualTo(1));
|
||||
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
|
||||
// Check that it matches the total damage dealt
|
||||
Assert.That(msg.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Acts, Is.EqualTo((int) ThresholdActs.Breakage));
|
||||
Assert.That(threshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
|
||||
Assert.That(threshold.Spawn, Is.Not.Null);
|
||||
Assert.That(threshold.Spawn.Count, Is.EqualTo(1));
|
||||
Assert.That(threshold.Spawn.Single().Key, Is.EqualTo("WoodPlank"));
|
||||
Assert.That(threshold.Spawn.Single().Value.Min, Is.EqualTo(1));
|
||||
Assert.That(threshold.Spawn.Single().Value.Max, Is.EqualTo(1));
|
||||
Assert.That(threshold.SoundCollection, Is.Null.Or.Empty);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
// Reset thresholds reached
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Heal all damage
|
||||
sDamageableComponent.Heal();
|
||||
|
||||
// Damage up to 50
|
||||
sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true);
|
||||
|
||||
// Check that the total damage matches
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
// Both thresholds should have triggered
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Has.Exactly(2).Items);
|
||||
|
||||
// Verify the first one, should be the lowest one (20)
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[0];
|
||||
Assert.That(msg.ThresholdAmount, Is.EqualTo(20));
|
||||
|
||||
// The total damage should be 50
|
||||
Assert.That(msg.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Acts, Is.EqualTo(0));
|
||||
Assert.That(threshold.Sound, Is.Null.Or.Empty);
|
||||
Assert.That(threshold.Spawn, Is.Null);
|
||||
Assert.That(threshold.SoundCollection, Is.Null.Or.Empty);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
// Verify the second one, should be the highest one (50)
|
||||
msg = sThresholdListenerComponent.ThresholdsReached[1];
|
||||
Assert.That(msg.ThresholdAmount, Is.EqualTo(50));
|
||||
|
||||
// Check that it matches the total damage dealt
|
||||
Assert.That(msg.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
threshold = msg.Threshold;
|
||||
|
||||
// Check that it matches the YAML prototype
|
||||
Assert.That(threshold.Acts, Is.EqualTo((int) ThresholdActs.Breakage));
|
||||
Assert.That(threshold.Sound, Is.EqualTo("/Audio/Effects/woodhit.ogg"));
|
||||
Assert.That(threshold.Spawn, Is.Not.Null);
|
||||
Assert.That(threshold.Spawn.Count, Is.EqualTo(1));
|
||||
Assert.That(threshold.Spawn.Single().Key, Is.EqualTo("WoodPlank"));
|
||||
Assert.That(threshold.Spawn.Single().Value.Min, Is.EqualTo(1));
|
||||
Assert.That(threshold.Spawn.Single().Value.Max, Is.EqualTo(1));
|
||||
Assert.That(threshold.SoundCollection, Is.Null.Or.Empty);
|
||||
Assert.That(threshold.Triggered, Is.True);
|
||||
|
||||
// Reset thresholds reached
|
||||
sThresholdListenerComponent.ThresholdsReached.Clear();
|
||||
|
||||
// Heal the entity completely
|
||||
sDamageableComponent.Heal();
|
||||
|
||||
// Check that the entity has 0 damage
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(0));
|
||||
|
||||
// Set both thresholds to only trigger once
|
||||
foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values)
|
||||
{
|
||||
destructibleThreshold.TriggersOnce = true;
|
||||
}
|
||||
|
||||
// Damage the entity up to 50 damage again
|
||||
sDamageableComponent.ChangeDamage(DamageType.Blunt, 50, true);
|
||||
|
||||
// Check that the total damage matches
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
// No thresholds should have triggered as they were already triggered before, and they are set to only trigger once
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
|
||||
// Set both thresholds to trigger multiple times
|
||||
foreach (var destructibleThreshold in sDestructibleComponent.LowestToHighestThresholds.Values)
|
||||
{
|
||||
destructibleThreshold.TriggersOnce = false;
|
||||
}
|
||||
|
||||
// Check that the total damage matches
|
||||
Assert.That(sDamageableComponent.TotalDamage, Is.EqualTo(50));
|
||||
|
||||
// They shouldn't have been triggered by changing TriggersOnce
|
||||
Assert.That(sThresholdListenerComponent.ThresholdsReached, Is.Empty);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using Content.Server.GameObjects.EntitySystems.AI;
|
||||
using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer;
|
||||
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.AI;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -130,28 +131,18 @@ namespace Content.Server.AI.Utility.AiLogic
|
||||
_planCooldownRemaining = PlanCooldown;
|
||||
_blackboard = new Blackboard(SelfEntity);
|
||||
_planner = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiActionSystem>();
|
||||
if (SelfEntity.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||
{
|
||||
damageableComponent.HealthChangedEvent += DeathHandle;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
// TODO: If DamageableComponent removed still need to unsubscribe?
|
||||
if (SelfEntity.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||
{
|
||||
damageableComponent.HealthChangedEvent -= DeathHandle;
|
||||
}
|
||||
|
||||
var currentOp = CurrentAction?.ActionOperators.Peek();
|
||||
currentOp?.Shutdown(Outcome.Failed);
|
||||
}
|
||||
|
||||
private void DeathHandle(HealthChangedEventArgs eventArgs)
|
||||
public void MobStateChanged(MobStateChangedMessage message)
|
||||
{
|
||||
var oldDeadState = _isDead;
|
||||
_isDead = eventArgs.Damageable.CurrentState == DamageState.Dead || eventArgs.Damageable.CurrentState == DamageState.Critical;
|
||||
_isDead = message.Component.IsIncapacitated();
|
||||
|
||||
if (oldDeadState != _isDead)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
@@ -10,12 +11,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||
if (target == null || !target.TryGetComponent(out IMobStateComponent mobState))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (damageableComponent.CurrentState == DamageState.Critical)
|
||||
if (mobState.IsCritical())
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.AI.WorldState;
|
||||
using Content.Server.AI.WorldState.States;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
|
||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
@@ -10,12 +11,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
||||
{
|
||||
var target = context.GetState<TargetEntityState>().GetValue();
|
||||
|
||||
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||
if (target == null || !target.TryGetComponent(out IMobStateComponent mobState))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (damageableComponent.CurrentState == DamageState.Dead)
|
||||
if (mobState.IsDead())
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Administration;
|
||||
#nullable enable
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Observer;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
@@ -6,6 +7,7 @@ using Content.Server.Players;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -21,25 +23,25 @@ namespace Content.Server.Commands.Observer
|
||||
public string Help => "ghost";
|
||||
public bool CanReturn { get; set; } = true;
|
||||
|
||||
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
shell.SendText((IPlayerSession) null, "Nah");
|
||||
shell.SendText(player, "Nah");
|
||||
return;
|
||||
}
|
||||
|
||||
var mind = player.ContentData().Mind;
|
||||
var mind = player.ContentData()?.Mind;
|
||||
|
||||
if (mind == null)
|
||||
{
|
||||
shell.SendText(player, "You can't ghost here!");
|
||||
return;
|
||||
}
|
||||
|
||||
var canReturn = player.AttachedEntity != null && CanReturn;
|
||||
var name = player.AttachedEntity?.Name ?? player.Name;
|
||||
var playerEntity = player.AttachedEntity;
|
||||
|
||||
if (player.AttachedEntity != null && player.AttachedEntity.HasComponent<GhostComponent>())
|
||||
if (playerEntity != null && playerEntity.HasComponent<GhostComponent>())
|
||||
return;
|
||||
|
||||
if (mind.VisitingEntity != null)
|
||||
@@ -48,22 +50,28 @@ namespace Content.Server.Commands.Observer
|
||||
mind.VisitingEntity.Delete();
|
||||
}
|
||||
|
||||
var position = player.AttachedEntity?.Transform.Coordinates ?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint();
|
||||
var position = playerEntity?.Transform.Coordinates ?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint();
|
||||
var canReturn = false;
|
||||
|
||||
if (canReturn && player.AttachedEntity.TryGetComponent(out IDamageableComponent damageable))
|
||||
if (playerEntity != null && CanReturn && playerEntity.TryGetComponent(out IMobStateComponent? mobState))
|
||||
{
|
||||
switch (damageable.CurrentState)
|
||||
if (mobState.IsDead())
|
||||
{
|
||||
case DamageState.Dead:
|
||||
canReturn = true;
|
||||
break;
|
||||
case DamageState.Critical:
|
||||
}
|
||||
else if (mobState.IsCritical())
|
||||
{
|
||||
canReturn = true;
|
||||
damageable.ChangeDamage(DamageType.Asphyxiation, 100, true, null); //todo: what if they dont breathe lol
|
||||
break;
|
||||
default:
|
||||
|
||||
if (playerEntity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
//todo: what if they dont breathe lol
|
||||
damageable.ChangeDamage(DamageType.Asphyxiation, 100, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canReturn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +82,10 @@ namespace Content.Server.Commands.Observer
|
||||
var ghostComponent = ghost.GetComponent<GhostComponent>();
|
||||
ghostComponent.CanReturnToBody = canReturn;
|
||||
|
||||
if (player.AttachedEntity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent))
|
||||
if (playerEntity != null &&
|
||||
playerEntity.TryGetComponent(out ServerOverlayEffectsComponent? overlayComponent))
|
||||
{
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
overlayComponent.RemoveOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
}
|
||||
|
||||
if (canReturn)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Commands.Observer;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Part;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
@@ -76,10 +80,12 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
||||
{
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead)
|
||||
if (Owner.TryGetComponent(out IMobStateComponent? mobState) &&
|
||||
mobState.IsDead())
|
||||
{
|
||||
new Ghost().Execute(null, (IPlayerSession) session, null);
|
||||
var shell = IoCManager.Resolve<IConsoleShell>();
|
||||
|
||||
new Ghost().Execute(shell, (IPlayerSession) session, Array.Empty<string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
BodyPartType Part { get; }
|
||||
}
|
||||
|
||||
public class BodyHealthChangeParams : HealthChangeParams, IBodyHealthChangeParams
|
||||
public class BodyDamageChangeParams : DamageChangeParams, IBodyHealthChangeParams
|
||||
{
|
||||
public BodyHealthChangeParams(BodyPartType part)
|
||||
public BodyDamageChangeParams(BodyPartType part)
|
||||
{
|
||||
Part = part;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
[ComponentDependency] public readonly AppearanceComponent? AppearanceComponent = null;
|
||||
[ComponentDependency] private readonly ServerAlertsComponent? _serverAlertsComponent = null;
|
||||
[ComponentDependency] private readonly StunnableComponent? _stunnableComponent = null;
|
||||
[ComponentDependency] private readonly MobStateManagerComponent? _mobStateManagerComponent = null;
|
||||
[ComponentDependency] private readonly MobStateComponent? _mobStateComponent = null;
|
||||
|
||||
private int _size;
|
||||
|
||||
@@ -351,7 +351,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(Owner);
|
||||
}
|
||||
|
||||
_mobStateManagerComponent?.CurrentMobState.EnterState(Owner);
|
||||
_mobStateComponent?.CurrentState?.EnterState(Owner);
|
||||
|
||||
UpdateBuckleStatus();
|
||||
|
||||
|
||||
@@ -1,72 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
// TODO: Repair needs to set CurrentDamageState to DamageState.Alive, but it doesn't exist... should be easy enough if it's just an interface you can slap on BreakableComponent
|
||||
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and sets it to a "broken state" after taking
|
||||
/// enough damage.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class BreakableComponent : RuinableComponent, IExAct
|
||||
public class BreakableComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override string Name => "Breakable";
|
||||
|
||||
private ActSystem _actSystem;
|
||||
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new() {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
case ExplosionSeverity.Heavy:
|
||||
PerformDestruction();
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if (_random.Prob(0.5f))
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
// Might want to move this down and have a more standardized method of revival
|
||||
public void FixAllDamage()
|
||||
{
|
||||
Heal();
|
||||
CurrentState = DamageState.Alive;
|
||||
}
|
||||
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Construction;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -10,11 +8,8 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class BreakableConstructionComponent : RuinableComponent
|
||||
public class BreakableConstructionComponent : Component, IDestroyAct
|
||||
{
|
||||
private ActSystem _actSystem = default!;
|
||||
|
||||
public override string Name => "BreakableConstruction";
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
@@ -24,20 +19,16 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
serializer.DataField(this, x => x.Node, "node", string.Empty);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_actSystem = EntitySystem.Get<ActSystem>();
|
||||
}
|
||||
|
||||
public string Node { get; private set; } = string.Empty;
|
||||
|
||||
protected override async void DestructionBehavior()
|
||||
async void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (Owner.Deleted || !Owner.TryGetComponent(out ConstructionComponent? construction) || string.IsNullOrEmpty(Node)) return;
|
||||
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
if (Owner.Deleted ||
|
||||
!Owner.TryGetComponent(out ConstructionComponent? construction) ||
|
||||
string.IsNullOrEmpty(Node))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await construction.ChangeNode(Node);
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and deletes it after taking enough damage.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class DestructibleComponent : RuinableComponent, IDestroyAct
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
protected ActSystem ActSystem;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Destructible";
|
||||
|
||||
/// <summary>
|
||||
/// Entities spawned on destruction plus the min and max amount spawned.
|
||||
/// </summary>
|
||||
public Dictionary<string, MinMax> SpawnOnDestroy { get; private set; }
|
||||
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (SpawnOnDestroy == null || !eventArgs.IsSpawnWreck) return;
|
||||
foreach (var (key, value) in SpawnOnDestroy)
|
||||
{
|
||||
int count;
|
||||
if (value.Min >= value.Max)
|
||||
{
|
||||
count = value.Min;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = _random.Next(value.Min, value.Max + 1);
|
||||
}
|
||||
|
||||
if (count == 0) continue;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(key))
|
||||
{
|
||||
var spawned = Owner.EntityManager.SpawnEntity(key, Owner.Transform.Coordinates);
|
||||
var stack = spawned.GetComponent<StackComponent>();
|
||||
stack.Count = count;
|
||||
spawned.RandomOffset(0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var spawned = Owner.EntityManager.SpawnEntity(key, Owner.Transform.Coordinates);
|
||||
spawned.RandomOffset(0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
|
||||
serializer.DataField(this, d => d.SpawnOnDestroy, "spawnOnDestroy", null);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
ActSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
if (!Owner.Deleted)
|
||||
{
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
ActSystem.HandleDestruction(Owner,
|
||||
true); //This will call IDestroyAct.OnDestroy on this component (and all other components on this entity)
|
||||
}
|
||||
}
|
||||
|
||||
public struct MinMax
|
||||
{
|
||||
public int Min;
|
||||
public int Max;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and
|
||||
/// "ruins" or "destroys" it after enough damage is taken.
|
||||
/// </summary>
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public abstract class RuinableComponent : DamageableComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Sound played upon destruction.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
protected string DestroySound { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used instead of <see cref="DestroySound"/> if specified.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
protected string DestroySoundCollection { get; private set; }
|
||||
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new() {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"deadThreshold",
|
||||
100,
|
||||
t =>
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Thresholds[DamageState.Dead] = t.Value;
|
||||
},
|
||||
() => Thresholds.TryGetValue(DamageState.Dead, out var value) ? value : (int?) null);
|
||||
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySoundCollection, "destroySoundCollection", string.Empty);
|
||||
}
|
||||
|
||||
protected override void EnterState(DamageState state)
|
||||
{
|
||||
base.EnterState(state);
|
||||
|
||||
if (state == DamageState.Dead)
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the Owner <see cref="IEntity"/>, setting
|
||||
/// <see cref="IDamageableComponent.CurrentState"/> to
|
||||
/// <see cref="Shared.GameObjects.Components.Damage.DamageState.Dead"/>
|
||||
/// </summary>
|
||||
protected void PerformDestruction()
|
||||
{
|
||||
CurrentState = DamageState.Dead;
|
||||
|
||||
if (!Owner.Deleted)
|
||||
{
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
string sound = string.Empty;
|
||||
if (DestroySoundCollection != string.Empty)
|
||||
{
|
||||
sound = AudioHelpers.GetRandomFileFromSoundCollection(DestroySoundCollection);
|
||||
|
||||
}
|
||||
else if (DestroySound != string.Empty)
|
||||
{
|
||||
sound = DestroySound;
|
||||
}
|
||||
if (sound != string.Empty)
|
||||
{
|
||||
Logger.Debug("Playing destruction sound");
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(sound, pos, AudioHelpers.WithVariation(0.125f));
|
||||
}
|
||||
}
|
||||
|
||||
DestructionBehavior();
|
||||
}
|
||||
|
||||
protected abstract void DestructionBehavior();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public sealed class ActsFlags { }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage
|
||||
/// and triggers thresholds when reached.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DestructibleComponent : Component
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
private ActSystem _actSystem = default!;
|
||||
|
||||
public override string Name => "Destructible";
|
||||
|
||||
[ViewVariables]
|
||||
private SortedDictionary<int, Threshold> _lowestToHighestThresholds = new();
|
||||
|
||||
[ViewVariables] private int PreviousTotalDamage { get; set; }
|
||||
|
||||
public IReadOnlyDictionary<int, Threshold> LowestToHighestThresholds => _lowestToHighestThresholds;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"thresholds",
|
||||
new Dictionary<int, Threshold>(),
|
||||
thresholds => _lowestToHighestThresholds = new SortedDictionary<int, Threshold>(thresholds),
|
||||
() => new Dictionary<int, Threshold>(_lowestToHighestThresholds));
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_actSystem = EntitySystem.Get<ActSystem>();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
{
|
||||
if (msg.Damageable.Owner != Owner)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var (damage, threshold) in _lowestToHighestThresholds)
|
||||
{
|
||||
if (threshold.Triggered)
|
||||
{
|
||||
if (threshold.TriggersOnce)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (PreviousTotalDamage >= damage)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.Damageable.TotalDamage >= damage)
|
||||
{
|
||||
var thresholdMessage = new DestructibleThresholdReachedMessage(this, threshold, msg.Damageable.TotalDamage, damage);
|
||||
SendMessage(thresholdMessage);
|
||||
|
||||
threshold.Trigger(Owner, _random, _actSystem);
|
||||
}
|
||||
}
|
||||
|
||||
PreviousTotalDamage = msg.Damageable.TotalDamage;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public class DestructibleThresholdReachedMessage : ComponentMessage
|
||||
{
|
||||
public DestructibleThresholdReachedMessage(DestructibleComponent parent, Threshold threshold, int totalDamage, int thresholdAmount)
|
||||
{
|
||||
Parent = parent;
|
||||
Threshold = threshold;
|
||||
TotalDamage = totalDamage;
|
||||
ThresholdAmount = thresholdAmount;
|
||||
}
|
||||
|
||||
public DestructibleComponent Parent { get; }
|
||||
|
||||
public Threshold Threshold { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of total damage currently had that triggered this threshold.
|
||||
/// </summary>
|
||||
public int TotalDamage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of damage at which this threshold triggers.
|
||||
/// </summary>
|
||||
public int ThresholdAmount { get; }
|
||||
}
|
||||
}
|
||||
13
Content.Server/GameObjects/Components/Destructible/MinMax.cs
Normal file
13
Content.Server/GameObjects/Components/Destructible/MinMax.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public struct MinMax
|
||||
{
|
||||
[ViewVariables]
|
||||
public int Min;
|
||||
|
||||
[ViewVariables]
|
||||
public int Max;
|
||||
}
|
||||
}
|
||||
148
Content.Server/GameObjects/Components/Destructible/Threshold.cs
Normal file
148
Content.Server/GameObjects/Components/Destructible/Threshold.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
public class Threshold : IExposeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Entities spawned on reaching this threshold, from a min to a max.
|
||||
/// </summary>
|
||||
[ViewVariables] public Dictionary<string, MinMax>? Spawn;
|
||||
|
||||
/// <summary>
|
||||
/// Sound played upon destruction.
|
||||
/// </summary>
|
||||
[ViewVariables] public string Sound = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Used instead of <see cref="Sound"/> if specified.
|
||||
/// </summary>
|
||||
[ViewVariables] public string SoundCollection = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// What acts this threshold should trigger upon activation.
|
||||
/// See <see cref="ActSystem"/>.
|
||||
/// </summary>
|
||||
[ViewVariables] public int Acts;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this threshold has already been triggered.
|
||||
/// </summary>
|
||||
[ViewVariables] public bool Triggered;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this threshold only triggers once.
|
||||
/// If false, it will trigger again once the entity is healed
|
||||
/// and then damaged to reach this threshold once again.
|
||||
/// It will not repeatedly trigger as damage rises beyond that.
|
||||
/// </summary>
|
||||
[ViewVariables] public bool TriggersOnce;
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(ref Spawn, "Spawn", null);
|
||||
serializer.DataField(ref Sound, "Sound", string.Empty);
|
||||
serializer.DataField(ref SoundCollection, "SoundCollection", string.Empty);
|
||||
serializer.DataField(ref Acts, "Acts", 0, WithFormat.Flags<ActsFlags>());
|
||||
serializer.DataField(ref Triggered, "Triggered", false);
|
||||
serializer.DataField(ref TriggersOnce, "TriggersOnce", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers this threshold.
|
||||
/// </summary>
|
||||
/// <param name="owner">The entity that owns this threshold.</param>
|
||||
/// <param name="random">
|
||||
/// An instance of <see cref="IRobustRandom"/> to get randomness from, if relevant.
|
||||
/// </param>
|
||||
/// <param name="actSystem">
|
||||
/// An instance of <see cref="ActSystem"/> to call acts on, if relevant.
|
||||
/// </param>
|
||||
public void Trigger(IEntity owner, IRobustRandom random, ActSystem actSystem)
|
||||
{
|
||||
Triggered = true;
|
||||
|
||||
PlaySound(owner);
|
||||
DoSpawn(owner, random);
|
||||
DoActs(owner, actSystem);
|
||||
}
|
||||
|
||||
private void PlaySound(IEntity owner)
|
||||
{
|
||||
var pos = owner.Transform.Coordinates;
|
||||
var actualSound = string.Empty;
|
||||
|
||||
if (SoundCollection != string.Empty)
|
||||
{
|
||||
actualSound = AudioHelpers.GetRandomFileFromSoundCollection(SoundCollection);
|
||||
}
|
||||
else if (Sound != string.Empty)
|
||||
{
|
||||
actualSound = Sound;
|
||||
}
|
||||
|
||||
if (actualSound != string.Empty)
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(actualSound, pos, AudioHelpers.WithVariation(0.125f));
|
||||
}
|
||||
}
|
||||
|
||||
private void DoSpawn(IEntity owner, IRobustRandom random)
|
||||
{
|
||||
if (Spawn == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (key, value) in Spawn)
|
||||
{
|
||||
var count = value.Min >= value.Max
|
||||
? value.Min
|
||||
: random.Next(value.Min, value.Max + 1);
|
||||
|
||||
if (count == 0) continue;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(key))
|
||||
{
|
||||
var spawned = owner.EntityManager.SpawnEntity(key, owner.Transform.Coordinates);
|
||||
var stack = spawned.GetComponent<StackComponent>();
|
||||
stack.Count = count;
|
||||
spawned.RandomOffset(0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var spawned = owner.EntityManager.SpawnEntity(key, owner.Transform.Coordinates);
|
||||
spawned.RandomOffset(0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DoActs(IEntity owner, ActSystem acts)
|
||||
{
|
||||
if ((Acts & (int) ThresholdActs.Breakage) != 0)
|
||||
{
|
||||
acts.HandleBreakage(owner);
|
||||
}
|
||||
|
||||
if ((Acts & (int) ThresholdActs.Destruction) != 0)
|
||||
{
|
||||
acts.HandleDestruction(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Destructible
|
||||
{
|
||||
[Flags, FlagsFor(typeof(ActsFlags))]
|
||||
[Serializable]
|
||||
public enum ThresholdActs
|
||||
{
|
||||
Invalid = 0,
|
||||
Breakage,
|
||||
Destruction
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -139,11 +140,10 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!entity.TryGetComponent(out IPhysicsComponent? physics) ||
|
||||
!physics.CanCollide)
|
||||
{
|
||||
if (!(entity.TryGetComponent(out IDamageableComponent? damageState) && damageState.CurrentState == DamageState.Dead)) {
|
||||
if (!(entity.TryGetComponent(out IMobStateComponent? state) && state.IsDead())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Gravity;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
@@ -108,8 +109,11 @@ namespace Content.Server.GameObjects.Components.Gravity
|
||||
return false;
|
||||
|
||||
// Repair generator
|
||||
var breakable = Owner.GetComponent<BreakableComponent>();
|
||||
breakable.FixAllDamage();
|
||||
if (Owner.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
damageable.Heal();
|
||||
}
|
||||
|
||||
_intact = true;
|
||||
|
||||
Owner.PopupMessage(eventArgs.User,
|
||||
|
||||
@@ -9,6 +9,8 @@ using Content.Server.Mobs;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -163,8 +165,8 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
}
|
||||
|
||||
var dead =
|
||||
mind.OwnedEntity.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead;
|
||||
mind.OwnedEntity.TryGetComponent<IMobStateComponent>(out var state) &&
|
||||
state.IsDead();
|
||||
if (!dead) return;
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
@@ -146,14 +147,23 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
UserInterface?.SetState(newState);
|
||||
}
|
||||
|
||||
private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState)
|
||||
private MedicalScannerStatus GetStatusFromDamageState(IMobStateComponent state)
|
||||
{
|
||||
switch (damageState)
|
||||
if (state.IsAlive())
|
||||
{
|
||||
case DamageState.Alive: return MedicalScannerStatus.Green;
|
||||
case DamageState.Critical: return MedicalScannerStatus.Red;
|
||||
case DamageState.Dead: return MedicalScannerStatus.Death;
|
||||
default: throw new ArgumentException(nameof(damageState));
|
||||
return MedicalScannerStatus.Green;
|
||||
}
|
||||
else if (state.IsCritical())
|
||||
{
|
||||
return MedicalScannerStatus.Red;
|
||||
}
|
||||
else if (state.IsDead())
|
||||
{
|
||||
return MedicalScannerStatus.Death;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MedicalScannerStatus.Yellow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +172,11 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
if (Powered)
|
||||
{
|
||||
var body = _bodyContainer.ContainedEntity;
|
||||
return body == null
|
||||
var state = body?.GetComponentOrNull<IMobStateComponent>();
|
||||
|
||||
return state == null
|
||||
? MedicalScannerStatus.Open
|
||||
: GetStatusFromDamageState(body.GetComponent<IDamageableComponent>().CurrentState);
|
||||
: GetStatusFromDamageState(state);
|
||||
}
|
||||
|
||||
return MedicalScannerStatus.Off;
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.Chemistry;
|
||||
@@ -355,8 +356,8 @@ namespace Content.Server.GameObjects.Components.Metabolism
|
||||
/// </param>
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent<IDamageableComponent>(out var damageable) ||
|
||||
damageable.CurrentState == DamageState.Dead)
|
||||
if (!Owner.TryGetComponent<IMobStateComponent>(out var state) ||
|
||||
state.IsDead())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Mobs;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -174,8 +175,8 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
}
|
||||
|
||||
var dead =
|
||||
Owner.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead;
|
||||
Owner.TryGetComponent<IMobStateComponent>(out var state) &&
|
||||
state.IsDead();
|
||||
|
||||
if (!HasMind)
|
||||
{
|
||||
|
||||
@@ -9,20 +9,17 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class CriticalState : SharedCriticalState
|
||||
public class CriticalMobState : SharedCriticalMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerAlertsComponent status))
|
||||
{
|
||||
status.ShowAlert(AlertType.HumanCrit); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
@@ -38,12 +35,12 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
base.ExitState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,12 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class DeadState : SharedDeadState
|
||||
public class DeadMobState : SharedDeadMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
|
||||
@@ -44,6 +46,8 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
base.ExitState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out IPhysicsComponent physics))
|
||||
{
|
||||
physics.CanCollide = true;
|
||||
@@ -54,7 +58,5 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMobStateManagerComponent))]
|
||||
public class MobStateManagerComponent : SharedMobStateManagerComponent
|
||||
[ComponentReference(typeof(SharedMobStateComponent))]
|
||||
[ComponentReference(typeof(IMobStateComponent))]
|
||||
public class MobStateComponent : SharedMobStateComponent
|
||||
{
|
||||
private readonly Dictionary<DamageState, IMobState> _behavior = new()
|
||||
{
|
||||
{DamageState.Alive, new NormalState()},
|
||||
{DamageState.Critical, new CriticalState()},
|
||||
{DamageState.Dead, new DeadState()}
|
||||
};
|
||||
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
protected override IReadOnlyDictionary<DamageState, IMobState> Behavior => _behavior;
|
||||
|
||||
public override IMobState CurrentMobState { get; protected set; }
|
||||
|
||||
public override DamageState CurrentDamageState
|
||||
{
|
||||
get => _currentDamageState;
|
||||
protected set
|
||||
{
|
||||
if (_currentDamageState == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentDamageState != DamageState.Invalid)
|
||||
{
|
||||
CurrentMobState.ExitState(Owner);
|
||||
}
|
||||
|
||||
_currentDamageState = value;
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
// TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used
|
||||
base.OnRemove();
|
||||
|
||||
if (Owner.TryGetComponent(out ServerAlertsComponent status))
|
||||
{
|
||||
status.ClearAlert(AlertType.HumanHealth);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new MobStateManagerComponentState(CurrentDamageState);
|
||||
base.OnRemove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class NormalMobState : SharedNormalMobState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity, int threshold)
|
||||
{
|
||||
base.UpdateState(entity, threshold);
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent? damageable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out ServerAlertsComponent? alerts))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IMobStateComponent? stateComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
short modifier = 0;
|
||||
|
||||
if (stateComponent.TryGetEarliestIncapacitatedState(threshold, out _, out var earliestThreshold))
|
||||
{
|
||||
modifier = (short) (damageable.TotalDamage / (earliestThreshold / 7f));
|
||||
}
|
||||
|
||||
alerts.ShowAlert(AlertType.HumanHealth, modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class NormalState : SharedNormalState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
|
||||
}
|
||||
|
||||
UpdateState(entity);
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity) { }
|
||||
|
||||
public override void UpdateState(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out ServerAlertsComponent status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
status.ShowAlert(AlertType.HumanHealth, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
switch (damageable)
|
||||
{
|
||||
case RuinableComponent ruinable:
|
||||
{
|
||||
if (!ruinable.Thresholds.TryGetValue(DamageState.Dead, out var threshold))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var modifier = (short) (ruinable.TotalDamage / (threshold / 7f));
|
||||
|
||||
status.ShowAlert(AlertType.HumanHealth, modifier);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!damageable.Thresholds.TryGetValue(DamageState.Critical, out var threshold))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var modifier = (short) (damageable.TotalDamage / (threshold / 7f));
|
||||
|
||||
status.ShowAlert(AlertType.HumanHealth, modifier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#nullable enable
|
||||
using Content.Server.AI.Utility.AiLogic;
|
||||
using Content.Server.GameObjects.EntitySystems.AI;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.AI;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -38,7 +38,7 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
}
|
||||
}
|
||||
|
||||
public AiLogicProcessor? Processor { get; set; }
|
||||
public UtilityAi? Processor { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? StartingGearPrototype { get; set; }
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -185,15 +186,19 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
HungerThresholdEffect();
|
||||
Dirty();
|
||||
}
|
||||
if (_currentHungerThreshold == HungerThreshold.Dead)
|
||||
|
||||
if (_currentHungerThreshold != HungerThreshold.Dead)
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IMobStateComponent mobState))
|
||||
return;
|
||||
|
||||
if (!mobState.IsDead())
|
||||
{
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
if (damageable.CurrentState != DamageState.Dead)
|
||||
{
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||
}
|
||||
}
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +214,4 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
return new HungerComponentState(_currentHungerThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -183,18 +184,20 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
Dirty();
|
||||
}
|
||||
|
||||
if (_currentThirstThreshold == ThirstThreshold.Dead)
|
||||
{
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
if (damageable.CurrentState != DamageState.Dead)
|
||||
{
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_currentThirstThreshold != ThirstThreshold.Dead)
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
return;
|
||||
|
||||
if (!Owner.TryGetComponent(out IMobStateComponent mobState))
|
||||
return;
|
||||
|
||||
if (!mobState.IsDead())
|
||||
{
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetThirst()
|
||||
{
|
||||
@@ -208,5 +211,4 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
return new ThirstComponentState(_currentThirstThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.Mobs;
|
||||
using Content.Server.Mobs.Roles;
|
||||
using Content.Server.Mobs.Roles.Suspicion;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Components.Suspicion;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -60,8 +61,8 @@ namespace Content.Server.GameObjects.Components.Suspicion
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
return Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||
damageable.CurrentState == DamageState.Dead;
|
||||
return Owner.TryGetComponent(out IMobStateComponent? state) &&
|
||||
state.IsDead();
|
||||
}
|
||||
|
||||
public bool IsInnocent()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
@@ -10,7 +11,9 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameObjects.Components
|
||||
@@ -19,57 +22,46 @@ namespace Content.Server.GameObjects.Components
|
||||
[ComponentReference(typeof(SharedWindowComponent))]
|
||||
public class WindowComponent : SharedWindowComponent, IExamine, IInteractHand
|
||||
{
|
||||
private int? Damage
|
||||
private int _maxDamage;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
get
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _maxDamage, "maxDamage", 100);
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageableComponent)) return null;
|
||||
return damageableComponent.TotalDamage;
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
{
|
||||
var current = msg.Damageable.TotalDamage;
|
||||
UpdateVisuals(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int? MaxDamage
|
||||
private void UpdateVisuals(int currentDamage)
|
||||
{
|
||||
get
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageableComponent)) return null;
|
||||
return damageableComponent.Thresholds[DamageState.Dead];
|
||||
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / _maxDamage);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||
{
|
||||
damageableComponent.HealthChangedEvent += OnDamage;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDamage(HealthChangedEventArgs eventArgs)
|
||||
{
|
||||
int current = eventArgs.Damageable.TotalDamage;
|
||||
int max = eventArgs.Damageable.Thresholds[DamageState.Dead];
|
||||
if (eventArgs.Damageable.CurrentState == DamageState.Dead) return;
|
||||
UpdateVisuals(current, max);
|
||||
}
|
||||
|
||||
private void UpdateVisuals(int currentDamage, int maxDamage)
|
||||
{
|
||||
if (Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(WindowVisuals.Damage, (float) currentDamage / maxDamage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
int? damage = Damage;
|
||||
int? maxDamage = MaxDamage;
|
||||
if (damage == null || maxDamage == null) return;
|
||||
float fraction = ((damage == 0 || maxDamage == 0) ? 0f : (float) damage / maxDamage) ?? 0f;
|
||||
int level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 7), 5);
|
||||
var damage = Owner.GetComponentOrNull<IDamageableComponent>()?.TotalDamage;
|
||||
if (damage == null) return;
|
||||
var fraction = ((damage == 0 || _maxDamage == 0)
|
||||
? 0f
|
||||
: (float) damage / _maxDamage);
|
||||
var level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 7), 5);
|
||||
switch (level)
|
||||
{
|
||||
case 0:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.AI.Utility.AiLogic;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
@@ -38,6 +40,8 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
// To avoid modifying awakeAi while iterating over it.
|
||||
private readonly List<SleepAiMessage> _queuedSleepMessages = new();
|
||||
|
||||
private readonly List<MobStateChangedMessage> _queuedMobStateMessages = new();
|
||||
|
||||
public bool IsAwake(AiLogicProcessor processor) => _awakeAi.Contains(processor);
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -45,8 +49,9 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SleepAiMessage>(HandleAiSleep);
|
||||
SubscribeLocalEvent<MobStateChangedMessage>(MobStateChanged);
|
||||
|
||||
var processors = _reflectionManager.GetAllChildren<AiLogicProcessor>();
|
||||
var processors = _reflectionManager.GetAllChildren<UtilityAi>();
|
||||
foreach (var processor in processors)
|
||||
{
|
||||
var att = (AiLogicProcessorAttribute) Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute))!;
|
||||
@@ -63,6 +68,18 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
if (cvarMaxUpdates <= 0)
|
||||
return;
|
||||
|
||||
foreach (var message in _queuedMobStateMessages)
|
||||
{
|
||||
if (!message.Entity.TryGetComponent(out AiControllerComponent? controller))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
controller.Processor?.MobStateChanged(message);
|
||||
}
|
||||
|
||||
_queuedMobStateMessages.Clear();
|
||||
|
||||
foreach (var message in _queuedSleepMessages)
|
||||
{
|
||||
switch (message.Sleep)
|
||||
@@ -118,6 +135,16 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
_queuedSleepMessages.Add(message);
|
||||
}
|
||||
|
||||
private void MobStateChanged(MobStateChangedMessage message)
|
||||
{
|
||||
if (!message.Entity.HasComponent<AiControllerComponent>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_queuedMobStateMessages.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will start up the controller's processor if not already done so.
|
||||
/// Also add them to the awakeAi for updates.
|
||||
@@ -132,11 +159,11 @@ namespace Content.Server.GameObjects.EntitySystems.AI
|
||||
_awakeAi.Add(controller.Processor);
|
||||
}
|
||||
|
||||
private AiLogicProcessor CreateProcessor(string name)
|
||||
private UtilityAi CreateProcessor(string name)
|
||||
{
|
||||
if (_processorTypes.TryGetValue(name, out var type))
|
||||
{
|
||||
return (AiLogicProcessor)_typeFactory.CreateInstance(type);
|
||||
return (UtilityAi)_typeFactory.CreateInstance(type);
|
||||
}
|
||||
|
||||
// processor needs to inherit AiLogicProcessor, and needs an AiLogicProcessorAttribute to define the YAML name
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
||||
AsTask = Tcs.Task;
|
||||
}
|
||||
|
||||
public void HandleDamage(HealthChangedEventArgs args)
|
||||
public void HandleDamage(DamageChangedEventArgs args)
|
||||
{
|
||||
_tookDamage = true;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Interfaces.Chat;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Shared;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -36,7 +37,7 @@ namespace Content.Server.GameTicking.GameRules
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("The game is now a death match. Kill everybody else to win!"));
|
||||
|
||||
_entityManager.EventBus.SubscribeEvent<HealthChangedEventArgs>(EventSource.Local, this, OnHealthChanged);
|
||||
_entityManager.EventBus.SubscribeEvent<DamageChangedEventArgs>(EventSource.Local, this, OnHealthChanged);
|
||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
@@ -44,11 +45,11 @@ namespace Content.Server.GameTicking.GameRules
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
_entityManager.EventBus.UnsubscribeEvent<HealthChangedEventArgs>(EventSource.Local, this);
|
||||
_entityManager.EventBus.UnsubscribeEvent<DamageChangedEventArgs>(EventSource.Local, this);
|
||||
_playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnHealthChanged(HealthChangedEventArgs message)
|
||||
private void OnHealthChanged(DamageChangedEventArgs message)
|
||||
{
|
||||
_runDelayedCheck();
|
||||
}
|
||||
@@ -63,13 +64,14 @@ namespace Content.Server.GameTicking.GameRules
|
||||
IPlayerSession winner = null;
|
||||
foreach (var playerSession in _playerManager.GetAllPlayers())
|
||||
{
|
||||
if (playerSession.AttachedEntity == null
|
||||
|| !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable))
|
||||
var playerEntity = playerSession.AttachedEntity;
|
||||
if (playerEntity == null
|
||||
|| !playerEntity.TryGetComponent(out IMobStateComponent state))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (damageable.CurrentState != DamageState.Alive)
|
||||
if (!state.IsAlive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.Mobs.Roles.Suspicion;
|
||||
using Content.Server.Players;
|
||||
using Content.Shared;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -111,13 +112,13 @@ namespace Content.Server.GameTicking.GameRules
|
||||
foreach (var playerSession in _playerManager.GetAllPlayers())
|
||||
{
|
||||
if (playerSession.AttachedEntity == null
|
||||
|| !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable)
|
||||
|| !playerSession.AttachedEntity.TryGetComponent(out IMobStateComponent mobState)
|
||||
|| !playerSession.AttachedEntity.HasComponent<SuspicionRoleComponent>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (damageable.CurrentState != DamageState.Alive)
|
||||
if (!mobState.IsAlive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
@@ -58,7 +59,11 @@ namespace Content.Server.GlobalVerbs
|
||||
if (target.TryGetComponent(out IDamageableComponent damage))
|
||||
{
|
||||
damage.Heal();
|
||||
damage.CurrentState = DamageState.Alive;
|
||||
}
|
||||
|
||||
if (target.TryGetComponent(out IMobStateComponent mobState))
|
||||
{
|
||||
mobState.UpdateState(0);
|
||||
}
|
||||
|
||||
if (target.TryGetComponent(out HungerComponent hunger))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable enable
|
||||
using Content.Server.Mobs;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -24,9 +24,10 @@ namespace Content.Server.Objectives.Conditions
|
||||
|
||||
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResourcePath("Mobs/Ghosts/ghost_human.rsi"), "icon");
|
||||
|
||||
public float Progress => _mind?.OwnedEntity != null &&
|
||||
_mind.OwnedEntity.TryGetComponent<IDamageableComponent>(out var damageableComponent) &&
|
||||
damageableComponent.CurrentState != DamageState.Dead
|
||||
public float Progress => _mind?
|
||||
.OwnedEntity?
|
||||
.GetComponentOrNull<IMobStateComponent>()?
|
||||
.IsDead() ?? false
|
||||
? 0f
|
||||
: 1f;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable enable
|
||||
using Content.Server.Mobs;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace Content.Server.Objectives.Conditions
|
||||
|
||||
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResourcePath("Objects/Weapons/Guns/Pistols/mk58_wood.rsi"), "icon");
|
||||
|
||||
public float Progress => Target?.OwnedEntity != null &&
|
||||
Target.OwnedEntity
|
||||
.TryGetComponent<IDamageableComponent>(out var damageableComponent) &&
|
||||
damageableComponent.CurrentState == DamageState.Dead
|
||||
public float Progress => Target?
|
||||
.OwnedEntity?
|
||||
.GetComponentOrNull<IMobStateComponent>()?
|
||||
.IsDead() ?? false
|
||||
? 1f
|
||||
: 0f;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
@@ -20,10 +20,7 @@ namespace Content.Server.Objectives.Conditions
|
||||
var allHumans = entityMgr.ComponentManager.EntityQuery<MindComponent>().Where(mc =>
|
||||
{
|
||||
var entity = mc.Mind?.OwnedEntity;
|
||||
return entity != null &&
|
||||
entity.TryGetComponent<IDamageableComponent>(out var damageableComponent) &&
|
||||
damageableComponent.CurrentState == DamageState.Alive
|
||||
&& mc.Mind != mind;
|
||||
return (entity?.GetComponentOrNull<IMobStateComponent>()?.IsAlive() ?? false) && mc.Mind != mind;
|
||||
}).Select(mc => mc.Mind).ToList();
|
||||
return new KillRandomPersonCondition {Target = IoCManager.Resolve<IRobustRandom>().Pick(allHumans)};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable enable
|
||||
using Content.Server.Mobs;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -24,9 +24,10 @@ namespace Content.Server.Objectives.Conditions
|
||||
|
||||
public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResourcePath("Objects/Misc/skub.rsi"), "icon"); //didn't know what else would have been a good icon for staying alive
|
||||
|
||||
public float Progress => _mind?.OwnedEntity != null &&
|
||||
_mind.OwnedEntity.TryGetComponent<IDamageableComponent>(out var damageableComponent) &&
|
||||
damageableComponent.CurrentState == DamageState.Dead
|
||||
public float Progress => _mind?
|
||||
.OwnedEntity?
|
||||
.GetComponentOrNull<IMobStateComponent>()?
|
||||
.IsDead() ?? false
|
||||
? 0f
|
||||
: 1f;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -40,17 +41,23 @@ namespace Content.Server.StationEvents
|
||||
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||
foreach (var player in playerManager.GetAllPlayers())
|
||||
{
|
||||
if (player.AttachedEntity == null || !player.AttachedEntity.TryGetComponent(out InventoryComponent? inventory)) return;
|
||||
var playerEntity = player.AttachedEntity;
|
||||
if (playerEntity == null || !playerEntity.TryGetComponent(out InventoryComponent? inventory)) return;
|
||||
if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.BELT, out ItemComponent? item)
|
||||
&& item?.Owner.Prototype?.ID == "UtilityBeltClothingFilledEvent") return;
|
||||
if (player.AttachedEntity.TryGetComponent<IDamageableComponent>(out var damageable)
|
||||
&& damageable.CurrentState == DamageState.Dead) return;
|
||||
if (playerEntity.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||
playerEntity.TryGetComponent(out IMobStateComponent? mobState) &&
|
||||
mobState.IsDead())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var playerPos = player.AttachedEntity.Transform.Coordinates;
|
||||
var playerPos = playerEntity.Transform.Coordinates;
|
||||
entityManager.SpawnEntity("UtilityBeltClothingFilledEvent", playerPos);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
@@ -59,6 +66,7 @@ namespace Content.Server.StationEvents
|
||||
var componentManager = IoCManager.Resolve<IComponentManager>();
|
||||
foreach (var component in componentManager.EntityQuery<AirlockComponent>()) component.BoltsDown = false;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!Running)
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Content.Shared.Damage
|
||||
|
||||
public static class DamageClassExtensions
|
||||
{
|
||||
// TODO DAMAGE This but not hardcoded
|
||||
private static readonly ImmutableDictionary<DamageClass, List<DamageType>> ClassToType =
|
||||
new Dictionary<DamageClass, List<DamageType>>
|
||||
{
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.Damage.DamageContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information regarding the various forms of damage an object has
|
||||
/// taken (i.e. brute, burn, or toxic damage).
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageContainer
|
||||
{
|
||||
private Dictionary<DamageType, int> _damageList = DamageTypeExtensions.ToDictionary();
|
||||
|
||||
public delegate void HealthChangedDelegate(List<HealthChangeData> changes);
|
||||
|
||||
[NonSerialized] public readonly HealthChangedDelegate OnHealthChanged;
|
||||
|
||||
public DamageContainer(HealthChangedDelegate onHealthChanged, DamageContainerPrototype data)
|
||||
{
|
||||
ID = data.ID;
|
||||
OnHealthChanged = onHealthChanged;
|
||||
SupportedClasses = data.ActiveDamageClasses;
|
||||
}
|
||||
|
||||
public string ID { get; }
|
||||
|
||||
[ViewVariables] public virtual List<DamageClass> SupportedClasses { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public virtual List<DamageType> SupportedTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
var toReturn = new List<DamageType>();
|
||||
foreach (var @class in SupportedClasses)
|
||||
{
|
||||
toReturn.AddRange(@class.ToTypes());
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sum of all damages kept on record.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int TotalDamage => _damageList.Values.Sum();
|
||||
|
||||
public IReadOnlyDictionary<DamageClass, int> DamageClasses =>
|
||||
DamageTypeExtensions.ToClassDictionary(DamageTypes);
|
||||
|
||||
public IReadOnlyDictionary<DamageType, int> DamageTypes => _damageList;
|
||||
|
||||
public bool SupportsDamageClass(DamageClass @class)
|
||||
{
|
||||
return SupportedClasses.Contains(@class);
|
||||
}
|
||||
|
||||
public bool SupportsDamageType(DamageType type)
|
||||
{
|
||||
return SupportedClasses.Contains(type.ToClass());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to grab the damage value for the given <see cref="DamageType"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// False if the container does not support that type, true otherwise.
|
||||
/// </returns>
|
||||
public bool TryGetDamageValue(DamageType type, [NotNullWhen(true)] out int damage)
|
||||
{
|
||||
return _damageList.TryGetValue(type, out damage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the damage value for the given <see cref="DamageType"/>.
|
||||
/// </summary>
|
||||
public int GetDamageValue(DamageType type)
|
||||
{
|
||||
return TryGetDamageValue(type, out var damage) ? damage : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to grab the sum of damage values for the given
|
||||
/// <see cref="DamageClasses"/>.
|
||||
/// </summary>
|
||||
/// <param name="class">The class to get the sum for.</param>
|
||||
/// <param name="damage">The resulting amount of damage, if any.</param>
|
||||
/// <returns>
|
||||
/// True if the class is supported in this container, false otherwise.
|
||||
/// </returns>
|
||||
public bool TryGetDamageClassSum(DamageClass @class, [NotNullWhen(true)] out int damage)
|
||||
{
|
||||
damage = 0;
|
||||
|
||||
if (SupportsDamageClass(@class))
|
||||
{
|
||||
foreach (var type in @class.ToTypes())
|
||||
{
|
||||
damage += GetDamageValue(type);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the sum of damage values for the given <see cref="DamageClasses"/>.
|
||||
/// </summary>
|
||||
public int GetDamageClassSum(DamageClass damageClass)
|
||||
{
|
||||
var sum = 0;
|
||||
|
||||
foreach (var type in damageClass.ToTypes())
|
||||
{
|
||||
sum += GetDamageValue(type);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to change 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 TryChangeDamageValue(DamageType type, int delta)
|
||||
{
|
||||
var damageClass = type.ToClass();
|
||||
|
||||
if (SupportsDamageClass(damageClass))
|
||||
{
|
||||
var current = _damageList[type];
|
||||
current = _damageList[type] = current + delta;
|
||||
|
||||
if (_damageList[type] < 0)
|
||||
{
|
||||
_damageList[type] = 0;
|
||||
delta = -current;
|
||||
current = 0;
|
||||
}
|
||||
|
||||
var datum = new HealthChangeData(type, current, delta);
|
||||
var data = new List<HealthChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the damage value for the given <see cref="DamageType"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of damage to change.</param>
|
||||
/// <param name="delta">The amount to change it by.</param>
|
||||
/// <param name="quiet">
|
||||
/// Whether or not to suppress the health change event.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if successful, false if this container does not support that type.
|
||||
/// </returns>
|
||||
public bool ChangeDamageValue(DamageType type, int delta, bool quiet = false)
|
||||
{
|
||||
if (!_damageList.TryGetValue(type, out var current))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_damageList[type] = current + delta;
|
||||
|
||||
if (_damageList[type] < 0)
|
||||
{
|
||||
_damageList[type] = 0;
|
||||
delta = -current;
|
||||
}
|
||||
|
||||
current = _damageList[type];
|
||||
|
||||
var datum = new HealthChangeData(type, current, delta);
|
||||
var data = new List<HealthChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
|
||||
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 TrySetDamageValue(DamageType type, int newValue)
|
||||
{
|
||||
if (newValue < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var damageClass = type.ToClass();
|
||||
|
||||
if (SupportedClasses.Contains(damageClass))
|
||||
{
|
||||
var old = _damageList[type] = newValue;
|
||||
_damageList[type] = newValue;
|
||||
|
||||
var delta = newValue - old;
|
||||
var datum = new HealthChangeData(type, newValue, delta);
|
||||
var data = new List<HealthChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to set the damage value for the given <see cref="DamageType"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of damage to set.</param>
|
||||
/// <param name="newValue">The value to set it to.</param>
|
||||
/// <param name="quiet">
|
||||
/// Whether or not to suppress the health changed event.
|
||||
/// </param>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool SetDamageValue(DamageType type, int newValue, bool quiet = false)
|
||||
{
|
||||
if (newValue < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_damageList.ContainsKey(type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var old = _damageList[type];
|
||||
_damageList[type] = newValue;
|
||||
|
||||
if (!quiet)
|
||||
{
|
||||
var delta = newValue - old;
|
||||
var datum = new HealthChangeData(type, 0, delta);
|
||||
var data = new List<HealthChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Heal()
|
||||
{
|
||||
var data = new List<HealthChangeData>();
|
||||
|
||||
foreach (var type in SupportedTypes)
|
||||
{
|
||||
var delta = -GetDamageValue(type);
|
||||
var datum = new HealthChangeData(type, 0, delta);
|
||||
|
||||
data.Add(datum);
|
||||
SetDamageValue(type, 0, true);
|
||||
}
|
||||
|
||||
OnHealthChanged(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -14,18 +15,37 @@ namespace Content.Shared.Damage.DamageContainer
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageContainerPrototype : IPrototype, IIndexedPrototype
|
||||
{
|
||||
private List<DamageClass> _activeDamageClasses;
|
||||
private HashSet<DamageClass> _supportedClasses;
|
||||
private HashSet<DamageType> _supportedTypes;
|
||||
private string _id;
|
||||
|
||||
[ViewVariables] public List<DamageClass> ActiveDamageClasses => _activeDamageClasses;
|
||||
// TODO NET 5 IReadOnlySet
|
||||
[ViewVariables] public IReadOnlyCollection<DamageClass> SupportedClasses => _supportedClasses;
|
||||
|
||||
[ViewVariables] public IReadOnlyCollection<DamageType> SupportedTypes => _supportedTypes;
|
||||
|
||||
[ViewVariables] public string ID => _id;
|
||||
|
||||
public virtual void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
var serializer = YamlObjectSerializer.NewReader(mapping);
|
||||
|
||||
serializer.DataField(ref _id, "id", string.Empty);
|
||||
serializer.DataField(ref _activeDamageClasses, "activeDamageClasses", new List<DamageClass>());
|
||||
serializer.DataField(ref _supportedClasses, "supportedClasses", new HashSet<DamageClass>());
|
||||
serializer.DataField(ref _supportedTypes, "supportedTypes", new HashSet<DamageType>());
|
||||
|
||||
var originalTypes = _supportedTypes.ToArray();
|
||||
|
||||
foreach (var supportedClass in _supportedClasses)
|
||||
foreach (var supportedType in supportedClass.ToTypes())
|
||||
{
|
||||
_supportedTypes.Add(supportedType);
|
||||
}
|
||||
|
||||
foreach (var originalType in originalTypes)
|
||||
{
|
||||
_supportedClasses.Add(originalType.ToClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace Content.Shared.Damage
|
||||
{DamageType.Asphyxiation, DamageClass.Airloss},
|
||||
{DamageType.Bloodloss, DamageClass.Airloss},
|
||||
{DamageType.Cellular, DamageClass.Genetic}
|
||||
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
public static DamageClass ToClass(this DamageType type)
|
||||
|
||||
@@ -6,7 +6,9 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Shared.Damage.ResistanceSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Set of resistances used by damageable objects. Each DamageType has a multiplier and flat damage reduction value.
|
||||
/// Set of resistances used by damageable objects.
|
||||
/// Each <see cref="DamageType"/> has a multiplier and flat damage
|
||||
/// reduction value.
|
||||
/// </summary>
|
||||
[NetSerializable]
|
||||
[Serializable]
|
||||
@@ -33,7 +35,8 @@ namespace Content.Shared.Damage.ResistanceSet
|
||||
public string ID { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts input damage with the resistance set values. Only applies reduction if the amount is damage (positive), not
|
||||
/// Adjusts input damage with the resistance set values.
|
||||
/// Only applies reduction if the amount is damage (positive), not
|
||||
/// healing (negative).
|
||||
/// </summary>
|
||||
/// <param name="damageType">Type of damage.</param>
|
||||
@@ -59,8 +62,7 @@ namespace Content.Shared.Damage.ResistanceSet
|
||||
/// <summary>
|
||||
/// Settings for a specific damage type in a resistance set. Flat reduction is applied before the coefficient.
|
||||
/// </summary>
|
||||
[NetSerializable]
|
||||
[Serializable]
|
||||
[Serializable, NetSerializable]
|
||||
public struct ResistanceSetSettings
|
||||
{
|
||||
[ViewVariables] public float Coefficient { get; private set; }
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body.Part;
|
||||
using Content.Shared.GameObjects.Components.Body.Part.Property;
|
||||
using Content.Shared.GameObjects.Components.Body.Preset;
|
||||
@@ -103,8 +104,7 @@ namespace Content.Shared.GameObjects.Components.Body
|
||||
{
|
||||
if (part.IsVital && Parts.Count(x => x.Value.PartType == part.PartType) == 0)
|
||||
{
|
||||
damageable.CurrentState = DamageState.Dead;
|
||||
damageable.ForceHealthChangedEvent();
|
||||
damageable.ChangeDamage(DamageType.Bloodloss, 300, true); // TODO BODY KILL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Data class with information on how the value of a
|
||||
/// single <see cref="DamageType"/> has changed.
|
||||
/// </summary>
|
||||
public struct DamageChangeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of damage that changed.
|
||||
/// </summary>
|
||||
public DamageType Type;
|
||||
|
||||
/// <summary>
|
||||
/// The new current value for that damage.
|
||||
/// </summary>
|
||||
public int NewValue;
|
||||
|
||||
/// <summary>
|
||||
/// How much the health value changed from its last value (negative is heals, positive is damage).
|
||||
/// </summary>
|
||||
public int Delta;
|
||||
|
||||
public DamageChangeData(DamageType type, int newValue, int delta)
|
||||
{
|
||||
Type = type;
|
||||
NewValue = newValue;
|
||||
Delta = delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.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>
|
||||
public class DamageChangeParams : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
public class DamageChangedEventArgs : EventArgs
|
||||
{
|
||||
public DamageChangedEventArgs(IDamageableComponent damageable, IReadOnlyList<DamageChangeData> data)
|
||||
{
|
||||
Damageable = damageable;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public DamageChangedEventArgs(IDamageableComponent damageable, DamageType type, int newValue, int delta)
|
||||
{
|
||||
Damageable = damageable;
|
||||
|
||||
var datum = new DamageChangeData(type, newValue, delta);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="IDamageableComponent"/> that invoked the event.
|
||||
/// </summary>
|
||||
public IDamageableComponent Damageable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List containing data on each <see cref="DamageType"/> that was changed.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DamageChangeData> Data { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
public class DamageChangedMessage : ComponentMessage
|
||||
{
|
||||
public DamageChangedMessage(IDamageableComponent damageable, IReadOnlyList<DamageChangeData> data)
|
||||
{
|
||||
Damageable = damageable;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public DamageChangedMessage(IDamageableComponent damageable, DamageType type, int newValue, int delta)
|
||||
{
|
||||
Damageable = damageable;
|
||||
|
||||
var datum = new DamageChangeData(type, newValue, delta);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="IDamageableComponent"/> that invoked the event.
|
||||
/// </summary>
|
||||
public IDamageableComponent Damageable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List containing data on each <see cref="DamageType"/> that was changed.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DamageChangeData> Data { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
// TODO: Fix summary
|
||||
/// <summary>
|
||||
/// Defines what state an <see cref="IEntity"/> with a
|
||||
/// <see cref="IDamageableComponent"/> is in.
|
||||
/// Not all states must be supported - for instance, the
|
||||
/// <see cref="RuinableComponent"/> only supports
|
||||
/// <see cref="DamageState.Alive"/> and <see cref="DamageState.Dead"/>,
|
||||
/// as inanimate objects don't go into crit.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum DamageState
|
||||
{
|
||||
Invalid = 0,
|
||||
Alive,
|
||||
Critical,
|
||||
Dead
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.DamageContainer;
|
||||
using Content.Shared.Damage.ResistanceSet;
|
||||
@@ -31,51 +32,28 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
|
||||
public override string Name => "Damageable";
|
||||
|
||||
private DamageState _damageState;
|
||||
public override uint? NetID => ContentNetIDs.DAMAGEABLE;
|
||||
|
||||
private readonly Dictionary<DamageType, int> _damageList = DamageTypeExtensions.ToDictionary();
|
||||
private readonly HashSet<DamageType> _supportedTypes = new();
|
||||
private readonly HashSet<DamageClass> _supportedClasses = new();
|
||||
private DamageFlag _flags;
|
||||
|
||||
public event Action<HealthChangedEventArgs>? HealthChangedEvent;
|
||||
public event Action<DamageChangedEventArgs>? HealthChangedEvent;
|
||||
|
||||
// TODO DAMAGE Use as default values, specify overrides in a separate property through yaml for better (de)serialization
|
||||
[ViewVariables] public string DamageContainerId { get; set; } = default!;
|
||||
|
||||
[ViewVariables] private ResistanceSet Resistances { get; set; } = default!;
|
||||
|
||||
[ViewVariables] private DamageContainer Damage { get; set; } = default!;
|
||||
// TODO DAMAGE Cache this
|
||||
[ViewVariables] public int TotalDamage => _damageList.Values.Sum();
|
||||
|
||||
public Dictionary<DamageState, int> Thresholds { get; set; } = new();
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<DamageClass, int> DamageClasses =>
|
||||
DamageTypeExtensions.ToClassDictionary(_damageList);
|
||||
|
||||
public virtual List<DamageState> SupportedDamageStates
|
||||
{
|
||||
get
|
||||
{
|
||||
var states = new List<DamageState> {DamageState.Alive};
|
||||
|
||||
states.AddRange(Thresholds.Keys);
|
||||
|
||||
return states;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual DamageState CurrentState
|
||||
{
|
||||
get => _damageState;
|
||||
set
|
||||
{
|
||||
var old = _damageState;
|
||||
_damageState = value;
|
||||
|
||||
if (old != value)
|
||||
{
|
||||
EnterState(value);
|
||||
}
|
||||
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] public int TotalDamage => Damage.TotalDamage;
|
||||
|
||||
public IReadOnlyDictionary<DamageClass, int> DamageClasses => Damage.DamageClasses;
|
||||
|
||||
public IReadOnlyDictionary<DamageType, int> DamageTypes => Damage.DamageTypes;
|
||||
[ViewVariables] public IReadOnlyDictionary<DamageType, int> DamageTypes => _damageList;
|
||||
|
||||
public DamageFlag Flags
|
||||
{
|
||||
@@ -107,41 +85,20 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
Flags &= ~flag;
|
||||
}
|
||||
|
||||
public bool SupportsDamageClass(DamageClass @class)
|
||||
{
|
||||
return _supportedClasses.Contains(@class);
|
||||
}
|
||||
|
||||
public bool SupportsDamageType(DamageType type)
|
||||
{
|
||||
return _supportedTypes.Contains(type);
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
// TODO DAMAGE Serialize as a dictionary of damage states to thresholds
|
||||
serializer.DataReadWriteFunction(
|
||||
"criticalThreshold",
|
||||
null,
|
||||
t =>
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Thresholds[DamageState.Critical] = t.Value;
|
||||
},
|
||||
() => Thresholds.TryGetValue(DamageState.Critical, out var value) ? value : (int?) null);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"deadThreshold",
|
||||
null,
|
||||
t =>
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Thresholds[DamageState.Dead] = t.Value;
|
||||
},
|
||||
() => Thresholds.TryGetValue(DamageState.Dead, out var value) ? value : (int?) null);
|
||||
|
||||
serializer.DataField(ref _damageState, "damageState", DamageState.Alive);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"flags",
|
||||
new List<DamageFlag>(),
|
||||
@@ -161,7 +118,9 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
var writeFlags = new List<DamageFlag>();
|
||||
|
||||
if (Flags == DamageFlag.None)
|
||||
{
|
||||
return writeFlags;
|
||||
}
|
||||
|
||||
foreach (var flag in (DamageFlag[]) Enum.GetValues(typeof(DamageFlag)))
|
||||
{
|
||||
@@ -181,9 +140,15 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
prototype =>
|
||||
{
|
||||
var damagePrototype = _prototypeManager.Index<DamageContainerPrototype>(prototype);
|
||||
Damage = new DamageContainer(OnHealthChanged, damagePrototype);
|
||||
|
||||
_supportedClasses.Clear();
|
||||
_supportedTypes.Clear();
|
||||
|
||||
DamageContainerId = damagePrototype.ID;
|
||||
_supportedClasses.UnionWith(damagePrototype.SupportedClasses);
|
||||
_supportedTypes.UnionWith(damagePrototype.SupportedTypes);
|
||||
},
|
||||
() => Damage.ID);
|
||||
() => DamageContainerId);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"resistancePrototype",
|
||||
@@ -196,16 +161,6 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
() => Resistances.ID);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var behavior in Owner.GetAllComponents<IOnHealthChangedBehavior>())
|
||||
{
|
||||
HealthChangedEvent += behavior.OnHealthChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
@@ -213,29 +168,94 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
ForceHealthChangedEvent();
|
||||
}
|
||||
|
||||
public bool TryGetDamage(DamageType type, out int damage)
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return Damage.TryGetDamageValue(type, out damage);
|
||||
return new DamageableComponentState(_damageList, _flags);
|
||||
}
|
||||
|
||||
public bool ChangeDamage(DamageType type, int amount, bool ignoreResistances,
|
||||
IEntity? source = null,
|
||||
HealthChangeParams? extraParams = null)
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
if (amount > 0 && HasFlag(DamageFlag.Invulnerable))
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (!(curState is DamageableComponentState state))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_damageList.Clear();
|
||||
|
||||
foreach (var (type, damage) in state.DamageList)
|
||||
{
|
||||
_damageList[type] = damage;
|
||||
}
|
||||
|
||||
_flags = state.Flags;
|
||||
}
|
||||
|
||||
public int GetDamage(DamageType type)
|
||||
{
|
||||
return _damageList.GetValueOrDefault(type);
|
||||
}
|
||||
|
||||
public bool TryGetDamage(DamageType type, out int damage)
|
||||
{
|
||||
return _damageList.TryGetValue(type, out damage);
|
||||
}
|
||||
|
||||
public int GetDamage(DamageClass @class)
|
||||
{
|
||||
if (!SupportsDamageClass(@class))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var damage = 0;
|
||||
|
||||
foreach (var type in @class.ToTypes())
|
||||
{
|
||||
damage += GetDamage(type);
|
||||
}
|
||||
|
||||
return damage;
|
||||
}
|
||||
|
||||
public bool TryGetDamage(DamageClass @class, out int damage)
|
||||
{
|
||||
if (!SupportsDamageClass(@class))
|
||||
{
|
||||
damage = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
damage = GetDamage(@class);
|
||||
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)
|
||||
{
|
||||
if (newValue < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Damage.SupportsDamageType(type))
|
||||
{
|
||||
var finalDamage = amount;
|
||||
if (!ignoreResistances)
|
||||
{
|
||||
finalDamage = Resistances.CalculateDamage(type, amount);
|
||||
}
|
||||
var damageClass = type.ToClass();
|
||||
|
||||
Damage.ChangeDamageValue(type, finalDamage);
|
||||
if (_supportedClasses.Contains(damageClass))
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -243,17 +263,80 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ChangeDamage(DamageClass @class, int amount, bool ignoreResistances,
|
||||
public void Heal(DamageType type)
|
||||
{
|
||||
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,
|
||||
HealthChangeParams? extraParams = null)
|
||||
DamageChangeParams? extraParams = null)
|
||||
{
|
||||
if (amount > 0 && HasFlag(DamageFlag.Invulnerable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Damage.SupportsDamageClass(@class))
|
||||
if (!SupportsDamageType(type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var finalDamage = amount;
|
||||
|
||||
if (!ignoreResistances)
|
||||
{
|
||||
finalDamage = Resistances.CalculateDamage(type, amount);
|
||||
}
|
||||
|
||||
if (!_damageList.TryGetValue(type, out var current))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_damageList[type] = current + finalDamage;
|
||||
|
||||
if (_damageList[type] < 0)
|
||||
{
|
||||
_damageList[type] = 0;
|
||||
finalDamage = -current;
|
||||
}
|
||||
|
||||
current = _damageList[type];
|
||||
|
||||
var datum = new DamageChangeData(type, current, finalDamage);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChangeDamage(DamageClass @class, int amount, bool ignoreResistances,
|
||||
IEntity? source = null,
|
||||
DamageChangeParams? extraParams = null)
|
||||
{
|
||||
if (amount > 0 && HasFlag(DamageFlag.Invulnerable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SupportsDamageClass(@class))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var types = @class.ToTypes();
|
||||
|
||||
if (amount < 0)
|
||||
@@ -271,7 +354,7 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
healThisCycle = 0;
|
||||
|
||||
int healPerType;
|
||||
if (healingLeft > -types.Count && healingLeft < 0)
|
||||
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)
|
||||
@@ -287,10 +370,9 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
foreach (var type in types)
|
||||
{
|
||||
var healAmount =
|
||||
Math.Max(Math.Max(healPerType, -Damage.GetDamageValue(type)),
|
||||
healingLeft);
|
||||
Math.Max(Math.Max(healPerType, -GetDamage(type)), healingLeft);
|
||||
|
||||
Damage.ChangeDamageValue(type, healAmount);
|
||||
ChangeDamage(type, healAmount, true);
|
||||
healThisCycle += healAmount;
|
||||
healingLeft -= healAmount;
|
||||
}
|
||||
@@ -305,7 +387,7 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
int damagePerType;
|
||||
|
||||
if (damageLeft < types.Count && damageLeft > 0)
|
||||
if (damageLeft < types.Count)
|
||||
{
|
||||
damagePerType = 1;
|
||||
}
|
||||
@@ -317,7 +399,7 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
foreach (var type in types)
|
||||
{
|
||||
var damageAmount = Math.Min(damagePerType, damageLeft);
|
||||
Damage.ChangeDamageValue(type, damageAmount);
|
||||
ChangeDamage(type, damageAmount, true);
|
||||
damageLeft -= damageAmount;
|
||||
}
|
||||
}
|
||||
@@ -325,103 +407,63 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SetDamage(DamageType type, int newValue, IEntity? source = null,
|
||||
HealthChangeParams? extraParams = null)
|
||||
public bool SetDamage(DamageType type, int newValue, IEntity? source = null, DamageChangeParams? extraParams = null)
|
||||
{
|
||||
if (newValue >= TotalDamage && HasFlag(DamageFlag.Invulnerable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Damage.SupportsDamageType(type))
|
||||
if (newValue < 0)
|
||||
{
|
||||
Damage.SetDamageValue(type, newValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_damageList.ContainsKey(type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var old = _damageList[type];
|
||||
_damageList[type] = newValue;
|
||||
|
||||
var delta = newValue - old;
|
||||
var datum = new DamageChangeData(type, 0, delta);
|
||||
var data = new List<DamageChangeData> {datum};
|
||||
|
||||
OnHealthChanged(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Heal()
|
||||
{
|
||||
Damage.Heal();
|
||||
}
|
||||
|
||||
public void ForceHealthChangedEvent()
|
||||
{
|
||||
var data = new List<HealthChangeData>();
|
||||
var data = new List<DamageChangeData>();
|
||||
|
||||
foreach (var type in Damage.SupportedTypes)
|
||||
foreach (var type in _supportedTypes)
|
||||
{
|
||||
var damage = Damage.GetDamageValue(type);
|
||||
var datum = new HealthChangeData(type, damage, 0);
|
||||
var damage = GetDamage(type);
|
||||
var datum = new DamageChangeData(type, damage, 0);
|
||||
data.Add(datum);
|
||||
}
|
||||
|
||||
OnHealthChanged(data);
|
||||
}
|
||||
|
||||
public (int current, int max)? Health(DamageState threshold)
|
||||
private void OnHealthChanged(List<DamageChangeData> changes)
|
||||
{
|
||||
if (!SupportedDamageStates.Contains(threshold) ||
|
||||
!Thresholds.TryGetValue(threshold, out var thresholdValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var current = thresholdValue - TotalDamage;
|
||||
return (current, thresholdValue);
|
||||
}
|
||||
|
||||
public bool TryHealth(DamageState threshold, out (int current, int max) health)
|
||||
{
|
||||
var temp = Health(threshold);
|
||||
|
||||
if (temp == null)
|
||||
{
|
||||
health = (default, default);
|
||||
return false;
|
||||
}
|
||||
|
||||
health = temp.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnHealthChanged(List<HealthChangeData> changes)
|
||||
{
|
||||
var args = new HealthChangedEventArgs(this, changes);
|
||||
var args = new DamageChangedEventArgs(this, changes);
|
||||
OnHealthChanged(args);
|
||||
}
|
||||
|
||||
protected virtual void EnterState(DamageState state) { }
|
||||
|
||||
protected virtual void OnHealthChanged(HealthChangedEventArgs e)
|
||||
protected virtual void OnHealthChanged(DamageChangedEventArgs e)
|
||||
{
|
||||
if (CurrentState != DamageState.Dead)
|
||||
{
|
||||
if (Thresholds.TryGetValue(DamageState.Dead, out var deadThreshold) &&
|
||||
TotalDamage > deadThreshold)
|
||||
{
|
||||
CurrentState = DamageState.Dead;
|
||||
}
|
||||
else if (Thresholds.TryGetValue(DamageState.Critical, out var critThreshold) &&
|
||||
TotalDamage > critThreshold)
|
||||
{
|
||||
CurrentState = DamageState.Critical;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentState = DamageState.Alive;
|
||||
}
|
||||
}
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, e);
|
||||
HealthChangedEvent?.Invoke(e);
|
||||
|
||||
var message = new DamageChangedMessage(this, e.Data);
|
||||
SendMessage(message);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
@@ -446,4 +488,17 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
ChangeDamage(DamageType.Heat, damage, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class DamageableComponentState : ComponentState
|
||||
{
|
||||
public readonly Dictionary<DamageType, int> DamageList;
|
||||
public readonly DamageFlag Flags;
|
||||
|
||||
public DamageableComponentState(Dictionary<DamageType, int> damageList, DamageFlag flags) : base(ContentNetIDs.DAMAGEABLE)
|
||||
{
|
||||
DamageList = damageList;
|
||||
Flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
@@ -17,20 +15,7 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
/// (including both damage negated by resistance or simply inputting 0 as
|
||||
/// the amount of damage to deal).
|
||||
/// </summary>
|
||||
event Action<HealthChangedEventArgs> HealthChangedEvent;
|
||||
|
||||
Dictionary<DamageState, int> Thresholds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List of all <see cref="Damage.DamageState">DamageStates</see> that
|
||||
/// <see cref="CurrentState"/> can be.
|
||||
/// </summary>
|
||||
List<DamageState> SupportedDamageStates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Damage.DamageState"/> currently representing this component.
|
||||
/// </summary>
|
||||
DamageState CurrentState { get; set; }
|
||||
event Action<DamageChangedEventArgs> HealthChangedEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Sum of all damages taken.
|
||||
@@ -71,6 +56,10 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
/// <param name="flag">The flag to remove.</param>
|
||||
void RemoveFlag(DamageFlag flag);
|
||||
|
||||
bool SupportsDamageClass(DamageClass @class);
|
||||
|
||||
bool SupportsDamageType(DamageType type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of damage of a type.
|
||||
/// </summary>
|
||||
@@ -79,7 +68,7 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
/// <returns>
|
||||
/// True if the given <see cref="type"/> is supported, false otherwise.
|
||||
/// </returns>
|
||||
bool TryGetDamage(DamageType type, [NotNullWhen(true)] out int damage);
|
||||
bool TryGetDamage(DamageType type, out int damage);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the specified <see cref="DamageType"/>, applying
|
||||
@@ -101,10 +90,14 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// False if the given type is not supported or improper
|
||||
/// <see cref="HealthChangeParams"/> were provided; true otherwise.
|
||||
/// <see cref="DamageChangeParams"/> were provided; true otherwise.
|
||||
/// </returns>
|
||||
bool ChangeDamage(DamageType type, int amount, bool ignoreResistances, IEntity? source = null,
|
||||
HealthChangeParams? extraParams = null);
|
||||
bool ChangeDamage(
|
||||
DamageType type,
|
||||
int amount,
|
||||
bool ignoreResistances,
|
||||
IEntity? source = null,
|
||||
DamageChangeParams? extraParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the specified <see cref="DamageClass"/>, applying
|
||||
@@ -127,10 +120,14 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns false if the given class is not supported or improper
|
||||
/// <see cref="HealthChangeParams"/> were provided; true otherwise.
|
||||
/// <see cref="DamageChangeParams"/> were provided; true otherwise.
|
||||
/// </returns>
|
||||
bool ChangeDamage(DamageClass @class, int amount, bool ignoreResistances, IEntity? source = null,
|
||||
HealthChangeParams? extraParams = null);
|
||||
bool ChangeDamage(
|
||||
DamageClass @class,
|
||||
int amount,
|
||||
bool ignoreResistances,
|
||||
IEntity? source = null,
|
||||
DamageChangeParams? extraParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Forcefully sets the specified <see cref="DamageType"/> to the given
|
||||
@@ -145,9 +142,13 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns false if the given type is not supported or improper
|
||||
/// <see cref="HealthChangeParams"/> were provided; true otherwise.
|
||||
/// <see cref="DamageChangeParams"/> were provided; true otherwise.
|
||||
/// </returns>
|
||||
bool SetDamage(DamageType type, int newValue, IEntity? source = null, HealthChangeParams? extraParams = null);
|
||||
bool SetDamage(
|
||||
DamageType type,
|
||||
int newValue,
|
||||
IEntity? source = null,
|
||||
DamageChangeParams? extraParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Sets all damage values to zero.
|
||||
@@ -158,103 +159,5 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
/// Invokes the HealthChangedEvent with the current values of health.
|
||||
/// </summary>
|
||||
void ForceHealthChangedEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the health of an entity until it enters
|
||||
/// <see cref="threshold"/>.
|
||||
/// </summary>
|
||||
/// <param name="threshold">The state to use as a threshold.</param>
|
||||
/// <returns>
|
||||
/// The current and maximum health on this entity based on
|
||||
/// <see cref="threshold"/>, or null if the state is not supported.
|
||||
/// </returns>
|
||||
(int current, int max)? Health(DamageState threshold);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the health of an entity until it enters
|
||||
/// <see cref="threshold"/>.
|
||||
/// </summary>
|
||||
/// <param name="threshold">The state to use as a threshold.</param>
|
||||
/// <param name="health">
|
||||
/// The current and maximum health on this entity based on
|
||||
/// <see cref="threshold"/>, or null if the state is not supported.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if <see cref="threshold"/> is supported, false otherwise.
|
||||
/// </returns>
|
||||
bool TryHealth(DamageState threshold, [NotNullWhen(true)] out (int current, int max) health);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public class HealthChangeParams : EventArgs
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data class with information on how the <see cref="DamageType"/>
|
||||
/// values of a <see cref="IDamageableComponent"/> have changed.
|
||||
/// </summary>
|
||||
public class HealthChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="IDamageableComponent"/> that invoked the event.
|
||||
/// </summary>
|
||||
public readonly IDamageableComponent Damageable;
|
||||
|
||||
/// <summary>
|
||||
/// List containing data on each <see cref="DamageType"/> that was changed.
|
||||
/// </summary>
|
||||
public readonly List<HealthChangeData> Data;
|
||||
|
||||
public HealthChangedEventArgs(IDamageableComponent damageable, List<HealthChangeData> data)
|
||||
{
|
||||
Damageable = damageable;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public HealthChangedEventArgs(IDamageableComponent damageable, DamageType type, int newValue, int delta)
|
||||
{
|
||||
Damageable = damageable;
|
||||
|
||||
var datum = new HealthChangeData(type, newValue, delta);
|
||||
var data = new List<HealthChangeData> {datum};
|
||||
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data class with information on how the value of a
|
||||
/// single <see cref="DamageType"/> has changed.
|
||||
/// </summary>
|
||||
public struct HealthChangeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of damage that changed.
|
||||
/// </summary>
|
||||
public DamageType Type;
|
||||
|
||||
/// <summary>
|
||||
/// The new current value for that damage.
|
||||
/// </summary>
|
||||
public int NewValue;
|
||||
|
||||
/// <summary>
|
||||
/// How much the health value changed from its last value (negative is heals, positive is damage).
|
||||
/// </summary>
|
||||
public int Delta;
|
||||
|
||||
public HealthChangeData(DamageType type, int newValue, int delta)
|
||||
{
|
||||
Type = type;
|
||||
NewValue = newValue;
|
||||
Delta = delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
// TODO
|
||||
/// <summary>
|
||||
/// Component interface that gets triggered after the values of a
|
||||
/// <see cref="IDamageableComponent"/> on the same <see cref="IEntity"/> change.
|
||||
/// </summary>
|
||||
public interface IOnHealthChangedBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the entity's <see cref="IDamageableComponent"/>
|
||||
/// is healed or hurt.
|
||||
/// Of note is that a "deal 0 damage" call will still trigger
|
||||
/// this function (including both damage negated by resistance or
|
||||
/// simply inputting 0 as the amount of damage to deal).
|
||||
/// </summary>
|
||||
/// <param name="e">Details of how the health changed.</param>
|
||||
public void OnHealthChanged(HealthChangedEventArgs e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs
|
||||
{
|
||||
public static class DamageStateHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates over <see cref="DamageState"/>, returning them in order
|
||||
/// of alive to dead.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable of <see cref="DamageState"/>.</returns>
|
||||
public static IEnumerable<DamageState> AliveToDead()
|
||||
{
|
||||
foreach (DamageState state in Enum.GetValues(typeof(DamageState)))
|
||||
{
|
||||
if (state == DamageState.Invalid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum DamageStateVisuals
|
||||
{
|
||||
State
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines what state an <see cref="IEntity"/> is in.
|
||||
///
|
||||
/// Ordered from most alive to least alive.
|
||||
/// To enumerate them in this way see
|
||||
/// <see cref="DamageStateHelpers.AliveToDead"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum DamageState : byte
|
||||
{
|
||||
Invalid = 0,
|
||||
Alive = 1,
|
||||
Critical = 2,
|
||||
Dead = 3
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum DamageStateVisuals
|
||||
{
|
||||
State
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public abstract class BaseMobState : IMobState
|
||||
{
|
||||
protected abstract DamageState DamageState { get; }
|
||||
|
||||
public virtual bool IsAlive()
|
||||
{
|
||||
return DamageState == DamageState.Alive;
|
||||
}
|
||||
|
||||
public virtual bool IsCritical()
|
||||
{
|
||||
return DamageState == DamageState.Critical;
|
||||
}
|
||||
|
||||
public virtual bool IsDead()
|
||||
{
|
||||
return DamageState == DamageState.Dead;
|
||||
}
|
||||
|
||||
public virtual bool IsIncapacitated()
|
||||
{
|
||||
return IsCritical() || IsDead();
|
||||
}
|
||||
|
||||
public virtual void EnterState(IEntity entity) { }
|
||||
|
||||
public virtual void ExitState(IEntity entity) { }
|
||||
|
||||
public virtual void UpdateState(IEntity entity, int threshold) { }
|
||||
|
||||
public virtual void ExposeData(ObjectSerializer serializer) { }
|
||||
|
||||
public virtual bool CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanMove()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanThrow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanSpeak()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanDrop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanPickup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanEmote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanEquip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanUnequip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
@@ -9,8 +9,21 @@ namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
/// (i.e. Normal, Critical, Dead) and what effects to apply upon entering or
|
||||
/// exiting the state.
|
||||
/// </summary>
|
||||
public interface IMobState : IActionBlocker
|
||||
public interface IMobState : IExposeData, IActionBlocker
|
||||
{
|
||||
bool IsAlive();
|
||||
|
||||
bool IsCritical();
|
||||
|
||||
bool IsDead();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the mob is in a critical or dead state.
|
||||
/// See <see cref="IsCritical"/> and <see cref="IsDead"/>.
|
||||
/// </summary>
|
||||
/// <returns>true if it is, false otherwise.</returns>
|
||||
bool IsIncapacitated();
|
||||
|
||||
/// <summary>
|
||||
/// Called when this state is entered.
|
||||
/// </summary>
|
||||
@@ -24,6 +37,6 @@ namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
/// <summary>
|
||||
/// Called when this state is updated.
|
||||
/// </summary>
|
||||
void UpdateState(IEntity entity);
|
||||
void UpdateState(IEntity entity, int threshold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public interface IMobStateComponent : IComponent
|
||||
{
|
||||
IMobState? CurrentState { get; }
|
||||
|
||||
bool IsAlive();
|
||||
|
||||
bool IsCritical();
|
||||
|
||||
bool IsDead();
|
||||
|
||||
bool IsIncapacitated();
|
||||
|
||||
(IMobState state, int threshold)? GetEarliestIncapacitatedState(int minimumDamage);
|
||||
|
||||
bool TryGetEarliestIncapacitatedState(
|
||||
int minimumDamage,
|
||||
[NotNullWhen(true)] out IMobState? state,
|
||||
out int threshold);
|
||||
|
||||
void UpdateState(int damage, bool syncing = false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#nullable enable
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class MobStateChangedMessage : ComponentMessage
|
||||
{
|
||||
public MobStateChangedMessage(
|
||||
IMobStateComponent component,
|
||||
IMobState? oldMobState,
|
||||
IMobState currentMobState)
|
||||
{
|
||||
Component = component;
|
||||
OldMobState = oldMobState;
|
||||
CurrentMobState = currentMobState;
|
||||
}
|
||||
|
||||
public IEntity Entity => Component.Owner;
|
||||
|
||||
public IMobStateComponent Component { get; }
|
||||
|
||||
public IMobState? OldMobState { get; }
|
||||
|
||||
public IMobState CurrentMobState { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
/// <summary>
|
||||
/// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious).
|
||||
/// </summary>
|
||||
public abstract class SharedCriticalMobState : BaseMobState
|
||||
{
|
||||
protected override DamageState DamageState => DamageState.Critical;
|
||||
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
base.EnterState(entity);
|
||||
|
||||
if (entity.TryGetComponent(out SharedAlertsComponent status))
|
||||
{
|
||||
status.ShowAlert(AlertType.HumanCrit); // TODO: combine humancrit-0 and humancrit-1 into a gif and display it
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
base.ExitState(entity);
|
||||
|
||||
EntitySystem.Get<SharedStandingStateSystem>().Standing(entity);
|
||||
}
|
||||
|
||||
public override bool CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
/// <summary>
|
||||
/// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious).
|
||||
/// </summary>
|
||||
public abstract class SharedCriticalState : IMobState
|
||||
{
|
||||
public abstract void EnterState(IEntity entity);
|
||||
|
||||
public abstract void ExitState(IEntity entity);
|
||||
|
||||
public abstract void UpdateState(IEntity entity);
|
||||
|
||||
public bool CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public abstract class SharedDeadMobState : BaseMobState
|
||||
{
|
||||
protected override DamageState DamageState => DamageState.Dead;
|
||||
|
||||
public override bool CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanShiver()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanSweat()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public abstract class SharedDeadState : IMobState
|
||||
{
|
||||
public abstract void EnterState(IEntity entity);
|
||||
|
||||
public abstract void ExitState(IEntity entity);
|
||||
|
||||
public abstract void UpdateState(IEntity entity);
|
||||
|
||||
public bool CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanShiver() => false;
|
||||
public bool CanSweat() => false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IDamageableComponent"/>,
|
||||
/// this component will handle critical and death behaviors for mobs.
|
||||
/// Additionally, it handles sending effects to clients
|
||||
/// (such as blur effect for unconsciousness) and managing the health HUD.
|
||||
/// </summary>
|
||||
public abstract class SharedMobStateComponent : Component, IMobStateComponent, IActionBlocker
|
||||
{
|
||||
public override string Name => "MobState";
|
||||
|
||||
public override uint? NetID => ContentNetIDs.MOB_STATE;
|
||||
|
||||
/// <summary>
|
||||
/// States that this <see cref="SharedMobStateComponent"/> mapped to
|
||||
/// the amount of damage at which they are triggered.
|
||||
/// A threshold is reached when the total damage of an entity is equal
|
||||
/// to or higher than the int key, but lower than the next threshold.
|
||||
/// Ordered from lowest to highest.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private SortedDictionary<int, IMobState> _lowestToHighestStates = default!;
|
||||
|
||||
// TODO Remove Nullability?
|
||||
[ViewVariables]
|
||||
public IMobState? CurrentState { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public int? CurrentThreshold { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"thresholds",
|
||||
new Dictionary<int, IMobState>(),
|
||||
thresholds =>
|
||||
{
|
||||
_lowestToHighestStates = new SortedDictionary<int, IMobState>(thresholds);
|
||||
},
|
||||
() => new Dictionary<int, IMobState>(_lowestToHighestStates));
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (CurrentState != null && CurrentThreshold != null)
|
||||
{
|
||||
UpdateState(null, (CurrentState, CurrentThreshold.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
if (Owner.TryGetComponent(out SharedAlertsComponent? status))
|
||||
{
|
||||
status.ClearAlert(AlertType.HumanHealth);
|
||||
}
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new MobStateComponentState(CurrentThreshold);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not MobStateComponentState state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentThreshold == state.CurrentThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.CurrentThreshold == null)
|
||||
{
|
||||
RemoveState(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateState(state.CurrentThreshold.Value, true);
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case DamageChangedMessage msg:
|
||||
if (msg.Damageable.Owner != Owner)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateState(msg.Damageable.TotalDamage);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAlive()
|
||||
{
|
||||
return CurrentState?.IsAlive() ?? false;
|
||||
}
|
||||
|
||||
public bool IsCritical()
|
||||
{
|
||||
return CurrentState?.IsCritical() ?? false;
|
||||
}
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
return CurrentState?.IsDead() ?? false;
|
||||
}
|
||||
|
||||
public bool IsIncapacitated()
|
||||
{
|
||||
return CurrentState?.IsIncapacitated() ?? false;
|
||||
}
|
||||
|
||||
public (IMobState state, int threshold)? GetState(int damage)
|
||||
{
|
||||
foreach (var (threshold, state) in _lowestToHighestStates.Reverse())
|
||||
{
|
||||
if (damage >= threshold)
|
||||
{
|
||||
return (state, threshold);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetState(
|
||||
int damage,
|
||||
[NotNullWhen(true)] out IMobState? state,
|
||||
out int threshold)
|
||||
{
|
||||
var highestState = GetState(damage);
|
||||
|
||||
if (highestState == null)
|
||||
{
|
||||
state = default;
|
||||
threshold = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
(state, threshold) = highestState.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public (IMobState state, int threshold)? GetEarliestIncapacitatedState(int minimumDamage)
|
||||
{
|
||||
foreach (var (threshold, state) in _lowestToHighestStates)
|
||||
{
|
||||
if (!state.IsIncapacitated())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (threshold < minimumDamage)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return (state, threshold);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetEarliestIncapacitatedState(
|
||||
int minimumDamage,
|
||||
[NotNullWhen(true)] out IMobState? state,
|
||||
out int threshold)
|
||||
{
|
||||
var earliestState = GetEarliestIncapacitatedState(minimumDamage);
|
||||
|
||||
if (earliestState == null)
|
||||
{
|
||||
state = default;
|
||||
threshold = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
(state, threshold) = earliestState.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RemoveState(bool syncing = false)
|
||||
{
|
||||
var old = CurrentState;
|
||||
CurrentState = null;
|
||||
CurrentThreshold = null;
|
||||
|
||||
UpdateState(old, null);
|
||||
|
||||
if (!syncing)
|
||||
{
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(int damage, bool syncing = false)
|
||||
{
|
||||
if (!TryGetState(damage, out var newState, out var threshold))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateState(CurrentState, (newState, threshold));
|
||||
|
||||
if (!syncing)
|
||||
{
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateState(IMobState? old, (IMobState state, int threshold)? current)
|
||||
{
|
||||
if (!current.HasValue)
|
||||
{
|
||||
old?.ExitState(Owner);
|
||||
return;
|
||||
}
|
||||
|
||||
var (state, threshold) = current.Value;
|
||||
|
||||
CurrentThreshold = threshold;
|
||||
|
||||
if (state == old)
|
||||
{
|
||||
state.UpdateState(Owner, threshold);
|
||||
return;
|
||||
}
|
||||
|
||||
old?.ExitState(Owner);
|
||||
|
||||
CurrentState = state;
|
||||
|
||||
state.EnterState(Owner);
|
||||
state.UpdateState(Owner, threshold);
|
||||
|
||||
var message = new MobStateChangedMessage(this, old, state);
|
||||
|
||||
SendMessage(message);
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return CurrentState?.CanInteract() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return CurrentState?.CanMove() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return CurrentState?.CanUse() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return CurrentState?.CanThrow() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return CurrentState?.CanSpeak() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return CurrentState?.CanDrop() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return CurrentState?.CanPickup() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return CurrentState?.CanEmote() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return CurrentState?.CanAttack() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return CurrentState?.CanEquip() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return CurrentState?.CanUnequip() ?? true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return CurrentState?.CanChangeDirection() ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class MobStateComponentState : ComponentState
|
||||
{
|
||||
public readonly int? CurrentThreshold;
|
||||
|
||||
public MobStateComponentState(int? currentThreshold) : base(ContentNetIDs.MOB_STATE)
|
||||
{
|
||||
CurrentThreshold = currentThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
/// <summary>
|
||||
/// When attacked to an <see cref="IDamageableComponent"/>, this component will
|
||||
/// handle critical and death behaviors for mobs.
|
||||
/// Additionally, it handles sending effects to clients
|
||||
/// (such as blur effect for unconsciousness) and managing the health HUD.
|
||||
/// </summary>
|
||||
public abstract class SharedMobStateManagerComponent : Component, IOnHealthChangedBehavior, IActionBlocker
|
||||
{
|
||||
public override string Name => "MobStateManager";
|
||||
|
||||
public override uint? NetID => ContentNetIDs.MOB_STATE_MANAGER;
|
||||
|
||||
protected abstract IReadOnlyDictionary<DamageState, IMobState> Behavior { get; }
|
||||
|
||||
public virtual IMobState CurrentMobState { get; protected set; }
|
||||
|
||||
public virtual DamageState CurrentDamageState { get; protected set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
CurrentDamageState = DamageState.Alive;
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
CurrentMobState.UpdateState(Owner);
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return CurrentMobState.CanInteract();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return CurrentMobState.CanMove();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return CurrentMobState.CanUse();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return CurrentMobState.CanThrow();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return CurrentMobState.CanSpeak();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return CurrentMobState.CanDrop();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return CurrentMobState.CanPickup();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return CurrentMobState.CanEmote();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return CurrentMobState.CanAttack();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return CurrentMobState.CanEquip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return CurrentMobState.CanUnequip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return CurrentMobState.CanChangeDirection();
|
||||
}
|
||||
|
||||
public void OnHealthChanged(HealthChangedEventArgs e)
|
||||
{
|
||||
if (e.Damageable.CurrentState != CurrentDamageState)
|
||||
{
|
||||
CurrentDamageState = e.Damageable.CurrentState;
|
||||
CurrentMobState.ExitState(Owner);
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
}
|
||||
|
||||
CurrentMobState.UpdateState(Owner);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class MobStateManagerComponentState : ComponentState
|
||||
{
|
||||
public readonly DamageState DamageState;
|
||||
|
||||
public MobStateManagerComponentState(DamageState damageState) : base(ContentNetIDs.MOB_STATE_MANAGER)
|
||||
{
|
||||
DamageState = damageState;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
/// <summary>
|
||||
/// The standard state an entity is in; no negative effects.
|
||||
/// </summary>
|
||||
public abstract class SharedNormalMobState : BaseMobState
|
||||
{
|
||||
protected override DamageState DamageState => DamageState.Alive;
|
||||
|
||||
public override bool CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanMove()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanThrow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanSpeak()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanDrop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanPickup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanEmote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanEquip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanUnequip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Mobs.State
|
||||
{
|
||||
/// <summary>
|
||||
/// The standard state an entity is in; no negative effects.
|
||||
/// </summary>
|
||||
public abstract class SharedNormalState : IMobState
|
||||
{
|
||||
public abstract void EnterState(IEntity entity);
|
||||
|
||||
public abstract void ExitState(IEntity entity);
|
||||
|
||||
public abstract void UpdateState(IEntity entity);
|
||||
|
||||
public bool CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanMove()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanThrow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanSpeak()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanDrop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanPickup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanEmote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanEquip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanUnequip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@
|
||||
public const uint BATTERY_BARREL = 1067;
|
||||
public const uint SUSPICION_ROLE = 1068;
|
||||
public const uint ROTATION = 1069;
|
||||
public const uint MOB_STATE_MANAGER = 1070;
|
||||
public const uint MOB_STATE = 1070;
|
||||
public const uint SLIP = 1071;
|
||||
public const uint SPACE_VILLAIN_ARCADE = 1072;
|
||||
public const uint BLOCKGAME_ARCADE = 1073;
|
||||
@@ -86,6 +86,7 @@
|
||||
public const uint SINGULARITY = 1080;
|
||||
public const uint CHARACTERINFO = 1081;
|
||||
public const uint REAGENT_GRINDER = 1082;
|
||||
public const uint DAMAGEABLE = 1083;
|
||||
|
||||
// Net IDs for integration tests.
|
||||
public const uint PREDICTION_TEST = 10001;
|
||||
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.Map;
|
||||
namespace Content.Shared.GameObjects.EntitySystems
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface gives components behavior on getting destoyed.
|
||||
/// This interface gives components behavior on getting destroyed.
|
||||
/// </summary>
|
||||
public interface IDestroyAct
|
||||
{
|
||||
@@ -21,7 +21,6 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
public class DestructionEventArgs : EventArgs
|
||||
{
|
||||
public IEntity Owner { get; set; }
|
||||
public bool IsSpawnWreck { get; set; }
|
||||
}
|
||||
|
||||
public class BreakageEventArgs : EventArgs
|
||||
@@ -55,19 +54,20 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class ActSystem : EntitySystem
|
||||
{
|
||||
public void HandleDestruction(IEntity owner, bool isWreck)
|
||||
public void HandleDestruction(IEntity owner)
|
||||
{
|
||||
var eventArgs = new DestructionEventArgs
|
||||
{
|
||||
Owner = owner,
|
||||
IsSpawnWreck = isWreck
|
||||
Owner = owner
|
||||
};
|
||||
|
||||
var destroyActs = owner.GetAllComponents<IDestroyAct>().ToList();
|
||||
|
||||
foreach (var destroyAct in destroyActs)
|
||||
{
|
||||
destroyAct.OnDestroy(eventArgs);
|
||||
}
|
||||
|
||||
owner.Delete();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
# TODO BODY: Part damage
|
||||
- type: entity
|
||||
id: PartHuman
|
||||
name: "human body part"
|
||||
@@ -30,8 +31,8 @@
|
||||
# TODO BODY DettachableDamageableComponent?
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 100
|
||||
deadThreshold: 150
|
||||
# criticalThreshold: 100
|
||||
# deadThreshold: 150
|
||||
|
||||
- type: entity
|
||||
id: HeadHuman
|
||||
@@ -57,8 +58,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 50
|
||||
deadThreshold: 120
|
||||
# criticalThreshold: 50
|
||||
# deadThreshold: 120
|
||||
|
||||
- type: entity
|
||||
id: LeftArmHuman
|
||||
@@ -81,8 +82,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 40
|
||||
deadThreshold: 80
|
||||
# criticalThreshold: 40
|
||||
# deadThreshold: 80
|
||||
- type: Extension
|
||||
distance: 2.4
|
||||
|
||||
@@ -107,8 +108,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 40
|
||||
deadThreshold: 80
|
||||
# criticalThreshold: 40
|
||||
# deadThreshold: 80
|
||||
- type: Extension
|
||||
distance: 2.4
|
||||
|
||||
@@ -133,8 +134,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 30
|
||||
deadThreshold: 60
|
||||
# criticalThreshold: 30
|
||||
# deadThreshold: 60
|
||||
- type: Grasp
|
||||
|
||||
- type: entity
|
||||
@@ -151,8 +152,6 @@
|
||||
state: "r_hand"
|
||||
- type: BodyPart
|
||||
partType: Hand
|
||||
durability: 30
|
||||
destroyThreshold: -60
|
||||
size: 3
|
||||
compatibility: Biological
|
||||
symmetry: Right
|
||||
@@ -160,8 +159,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 30
|
||||
deadThreshold: 60
|
||||
# criticalThreshold: 30
|
||||
# deadThreshold: 60
|
||||
- type: Grasp
|
||||
|
||||
- type: entity
|
||||
@@ -185,8 +184,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 45
|
||||
deadThreshold: 90
|
||||
# criticalThreshold: 45
|
||||
# deadThreshold: 90
|
||||
- type: Leg
|
||||
speed: 2.6
|
||||
- type: Extension
|
||||
@@ -206,8 +205,6 @@
|
||||
state: "r_leg"
|
||||
- type: BodyPart
|
||||
partType: Leg
|
||||
durability: 45
|
||||
destroyThreshold: -90
|
||||
size: 6
|
||||
compatibility: Biological
|
||||
symmetry: Right
|
||||
@@ -215,8 +212,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 45
|
||||
deadThreshold: 90
|
||||
# criticalThreshold: 45
|
||||
# deadThreshold: 90
|
||||
- type: Leg
|
||||
speed: 2.6
|
||||
- type: Extension
|
||||
@@ -243,8 +240,8 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 30
|
||||
deadThreshold: 60
|
||||
# criticalThreshold: 30
|
||||
# deadThreshold: 60
|
||||
|
||||
- type: entity
|
||||
id: RightFootHuman
|
||||
@@ -267,5 +264,5 @@
|
||||
- type: Damageable
|
||||
damageContainer: biologicalDamageContainer
|
||||
resistances: defaultResistances
|
||||
criticalThreshold: 30
|
||||
deadThreshold: 60
|
||||
# criticalThreshold: 30
|
||||
# deadThreshold: 60
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: damageContainer
|
||||
id: biologicalDamageContainer
|
||||
activeDamageClasses:
|
||||
supportedClasses:
|
||||
- Brute
|
||||
- Burn
|
||||
- Toxin
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
- type: damageContainer
|
||||
id: metallicDamageContainer
|
||||
activeDamageClasses:
|
||||
supportedClasses:
|
||||
- Brute
|
||||
- Burn
|
||||
|
||||
@@ -56,9 +56,12 @@
|
||||
- type: Occluder
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Destructible
|
||||
deadThreshold: 500
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
500:
|
||||
Acts: ["Destruction"]
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
|
||||
|
||||
@@ -27,8 +27,11 @@
|
||||
- type: Strap
|
||||
position: Down
|
||||
rotation: -90
|
||||
- type: Destructible
|
||||
deadThreshold: 75
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
75:
|
||||
Acts: ["Destruction"]
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
- SmallImpassable
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
deadThreshold: 30
|
||||
destroySound: /Audio/Effects/woodhit.ogg
|
||||
spawnOnDestroy:
|
||||
thresholds:
|
||||
30:
|
||||
Sound: /Audio/Effects/woodhit.ogg
|
||||
Spawn:
|
||||
WoodPlank:
|
||||
Min: 1
|
||||
Max: 1
|
||||
resistances: metallicResistances
|
||||
Acts: ["Destruction"]
|
||||
- type: Occluder
|
||||
sizeX: 32
|
||||
sizeY: 32
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
- VaultImpassable
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Destructible
|
||||
deadThreshold: 50
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
50:
|
||||
Acts: ["Destruction"]
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.InstrumentUiKey.Key
|
||||
|
||||
@@ -11,9 +11,12 @@
|
||||
- type: Physics
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Destructible
|
||||
deadThreshold: 100
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
100:
|
||||
Acts: ["Destruction"]
|
||||
- type: ShuttleController
|
||||
- type: Strap
|
||||
position: Stand
|
||||
|
||||
@@ -30,9 +30,12 @@
|
||||
position: Stand
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
- type: Destructible
|
||||
deadThreshold: 50
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
50:
|
||||
Acts: ["Destruction"]
|
||||
|
||||
- type: entity
|
||||
name: chair
|
||||
|
||||
@@ -27,14 +27,17 @@
|
||||
- VaultImpassable
|
||||
- type: Pullable
|
||||
- type: Anchorable
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
deadThreshold: 30
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
spawnOnDestroy:
|
||||
thresholds:
|
||||
30:
|
||||
Sound: /Audio/Effects/metalbreak.ogg
|
||||
Spawn:
|
||||
SteelSheet1:
|
||||
Min: 1
|
||||
Max: 1
|
||||
resistances: metallicResistances
|
||||
Acts: ["Destruction"]
|
||||
|
||||
- type: entity
|
||||
id: Shelf
|
||||
@@ -65,11 +68,14 @@
|
||||
- VaultImpassable
|
||||
- type: Pullable
|
||||
- type: Anchorable
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
deadThreshold: 30
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
spawnOnDestroy:
|
||||
thresholds:
|
||||
30:
|
||||
Sound: /Audio/Effects/metalbreak.ogg
|
||||
Spawn:
|
||||
SteelSheet1:
|
||||
Min: 1
|
||||
Max: 1
|
||||
resistances: metallicResistances
|
||||
Acts: ["Destruction"]
|
||||
|
||||
@@ -35,14 +35,17 @@
|
||||
sprite: Constructible/Structures/Tables/generic.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/generic.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 15
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
15:
|
||||
Sound: /Audio/Effects/metalbreak.ogg
|
||||
Spawn:
|
||||
SteelSheet1:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
|
||||
- type: entity
|
||||
id: TableFrame
|
||||
@@ -54,14 +57,17 @@
|
||||
sprite: Constructible/Structures/Tables/frame.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/frame.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 1
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
1:
|
||||
Sound: /Audio/Effects/metalbreak.ogg
|
||||
Spawn:
|
||||
SteelSheet1:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
- type: Construction
|
||||
graph: Tables
|
||||
node: TableFrame
|
||||
@@ -76,14 +82,17 @@
|
||||
sprite: Constructible/Structures/Tables/bar.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/bar.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 1
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
1:
|
||||
Sound: /Audio/Effects/metalbreak.ogg
|
||||
Spawn:
|
||||
SteelSheet1:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
|
||||
- type: entity
|
||||
id: TableMetal
|
||||
@@ -95,14 +104,17 @@
|
||||
sprite: Constructible/Structures/Tables/metal.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/metal.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 15
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
15:
|
||||
Sound: /Audio/Effects/metalbreak.ogg
|
||||
Spawn:
|
||||
SteelSheet1:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
- type: Construction
|
||||
graph: Tables
|
||||
node: MetalTable
|
||||
@@ -117,14 +129,17 @@
|
||||
sprite: Constructible/Structures/Tables/reinforced.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/reinforced.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 75
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
75:
|
||||
Sound: /Audio/Effects/metalbreak.ogg
|
||||
Spawn:
|
||||
SteelSheet1:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
- type: Construction
|
||||
graph: Tables
|
||||
node: ReinforcedTable
|
||||
@@ -139,14 +154,17 @@
|
||||
sprite: Constructible/Structures/Tables/glass.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/glass.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 5
|
||||
destroySound: /Audio/Effects/glass_break2.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
5:
|
||||
Sound: /Audio/Effects/glass_break2.ogg
|
||||
Spawn:
|
||||
ShardGlass:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
- type: Construction
|
||||
graph: Tables
|
||||
node: GlassTable
|
||||
@@ -161,14 +179,17 @@
|
||||
sprite: Constructible/Structures/Tables/r_glass.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/r_glass.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 20
|
||||
destroySound: /Audio/Effects/glass_break2.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
20:
|
||||
Sound: /Audio/Effects/glass_break2.ogg
|
||||
Spawn:
|
||||
ShardGlass:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
- type: Construction
|
||||
graph: Tables
|
||||
node: RGlassTable
|
||||
@@ -183,14 +204,17 @@
|
||||
sprite: Constructible/Structures/Tables/wood.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/wood.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 15
|
||||
destroySound: /Audio/Effects/woodhit.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
15:
|
||||
Sound: /Audio/Effects/woodhit.ogg
|
||||
Spawn:
|
||||
WoodPlank:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
- type: Construction
|
||||
graph: Tables
|
||||
node: WoodTable
|
||||
@@ -205,14 +229,17 @@
|
||||
sprite: Constructible/Structures/Tables/carpet.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/carpet.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 15
|
||||
destroySound: /Audio/Effects/woodhit.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
spawnOnDestroy:
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
15:
|
||||
Sound: /Audio/Effects/woodhit.ogg
|
||||
Spawn:
|
||||
WoodPlank:
|
||||
Min: 1
|
||||
Max: 1
|
||||
Acts: ["Destruction"]
|
||||
- type: Construction
|
||||
graph: Tables
|
||||
node: PokerTable
|
||||
@@ -227,10 +254,13 @@
|
||||
sprite: Constructible/Structures/Tables/stone.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/stone.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 50
|
||||
destroySound: /Audio/Effects/picaxe2.ogg
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
50:
|
||||
Sound: /Audio/Effects/picaxe2.ogg
|
||||
Acts: ["Destruction"]
|
||||
|
||||
- type: entity
|
||||
id: TableDebug
|
||||
@@ -242,6 +272,9 @@
|
||||
sprite: Constructible/Structures/Tables/debug.rsi
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Tables/debug.rsi
|
||||
- type: Destructible
|
||||
deadThreshold: 1
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
1:
|
||||
Acts: ["Destruction"]
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Sprite
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholdvalue: 100
|
||||
thresholds:
|
||||
100:
|
||||
Acts: ["Destruction"]
|
||||
- type: GasCanisterPort
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Sprite
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholdvalue: 100
|
||||
thresholds:
|
||||
100:
|
||||
Acts: ["Destruction"]
|
||||
- type: GasCanister
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
state: spike
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
deadThreshold: 50
|
||||
thresholds:
|
||||
50:
|
||||
Acts: ["Destruction"]
|
||||
- type: KitchenSpike
|
||||
|
||||
@@ -11,8 +11,11 @@
|
||||
- type: Physics
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Damageable
|
||||
- type: Destructible
|
||||
thresholdvalue: 100
|
||||
thresholds:
|
||||
100:
|
||||
Acts: ["Destruction"]
|
||||
- type: Sprite
|
||||
- type: Appearance
|
||||
visuals:
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
- type: Physics
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Destructible
|
||||
thresholdvalue: 100
|
||||
- type: Damageable
|
||||
resistances: metallicResistances
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
100:
|
||||
Acts: ["Destruction"]
|
||||
- type: Sprite
|
||||
sprite: Constructible/Atmos/pump.rsi
|
||||
- type: Icon
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user