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:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user